Brief Description

In this document, we document the analysis of Weinreb and Rodriguez-Fraticelli et al., 2020, lineage tracing (LARRY) hematopoietic stem and progenitor cell differentiation in vitro. We use this dataset to showcase the application of Capybara, and the biological relevance of the hybrid cells. In this notebook, we first run Capybara on the LARRY dataset and identify key hybrid populations.

For details of the dataset, please refer to the Weinreb paper here (https://www.science.org/doi/10.1126/science.aaw3381?url_ver=Z39.88-2003&rfr_id=ori:rid:crossref.org&rfr_dat=cr_pub%20%200pubmed). For details of Capybara, please refer to the Capybara paper here (https://www.sciencedirect.com/science/article/pii/S1934590922000996?dgcid=coauthor).

Load libraries

library(Capybara)
library(ggplot2)

Load core functions for plotting and clonal data interpretation

plot.bar.chart <- function(frequency.table, filepath, width, height, label, to.file = FALSE) {
  
  freq.sort <- frequency.table[order(frequency.table$Freq, decreasing = T), ]
  freq.sort.sub <- freq.sort
  freq.sort.sub$Var1 <- as.character(freq.sort.sub$Var1)
  freq.sort.sub$Var1 <- factor(freq.sort.sub$Var1, levels = freq.sort.sub$Var1, ordered = T)
  
  
  p1 <- ggplot(freq.sort.sub, aes(x = label, y = Freq, fill = Var1)) +
    geom_bar(stat = "identity") + 
    scale_fill_brewer(palette = "Paired") +
    labs(x = "", y = "Percentage Composition") + 
    theme(legend.position = "right",
          axis.text.x = element_text(face = "bold", size = 12),
          axis.text.y = element_text(face = "bold", size = 12),
          axis.title.x = element_text(face = "bold.italic", size = 12),
          axis.title.y = element_text(face = "bold.italic", size = 12),
          panel.grid.major = element_blank(), 
          panel.grid.minor = element_blank(),
          panel.background = element_blank(), 
          title = element_text(face = "bold.italic", size = 12),
          axis.line.y = element_line(colour = "black"),
          axis.line.x = element_blank())
  if (to.file) {
    pdf(filepath, width = width, height = height, paper = "special")
    print(p1)
    dev.off()
  } else {
    print(p1)
  }
}
get.top.three.fates <- function(ind.clone.info, label.col) {
  if (nrow(ind.clone.info) == 1) {
      return(data.frame(Var1 = ind.clone.info[1,label.col], Freq = 100, stringsAsFactors = F))
  }
  freq.table <- as.data.frame(sort(table(ind.clone.info[,label.col]), decreasing = T) * 100/nrow(ind.clone.info))
  if (nrow(freq.table) <= 3) {
    if (nrow(freq.table) == 1) {
      return(data.frame(Var1 = rownames(freq.table), Freq = 100, stringsAsFactors = F))
    }
    return(freq.table)
  } else {
    return(freq.table[c(1:3),])
  }
}

compute.top.three.fates.for.all <- function(composite.clonal, label.col) {
  unique.clones.in.comp <- unique(composite.clonal$j)
  top.three.fates.df <- data.frame()
  for (uc.in.comp in unique.clones.in.comp) {
    curr.clone <- composite.clonal[which(composite.clonal$j == uc.in.comp),]
    top.fates <- get.top.three.fates(curr.clone, label.col)
    
    curr.top <- curr.clone[which(curr.clone[,label.col] %in% top.fates$Var1), ]
    if (nrow(top.three.fates.df) <= 0) {
      top.three.fates.df <- curr.top
    } else {
      top.three.fates.df <- rbind(top.three.fates.df, curr.top)
    }
  }
  
  return(top.three.fates.df)
}
compute.composition.clones <- function(clonal.data, label.col) {
  clones.uniq <- unique(clonal.data$j)
  composition.clones <- data.frame()
  for (uc in clones.uniq) {
    curr.clone.sub <- clonal.data[which(clonal.data$j == uc), ]
    curr.clone.sub$new.cell.type <- paste0("Day_", curr.clone.sub$tp, "_", curr.clone.sub[,label.col])
    curr.comp <- as.data.frame(table(curr.clone.sub$new.cell.type) * 100/nrow(curr.clone.sub))
    curr.comp$clone <- uc
    
    if (nrow(composition.clones) <= 0){
      composition.clones <- curr.comp
    } else {
      composition.clones <- rbind(composition.clones, curr.comp)
    }
  }
  
  return(composition.clones)
}

compute.composition.clones.d2.6 <- function(clonal.data, label.col) {
  clones.uniq <- unique(clonal.data$j)
  composition.clones <- data.frame()
  for (uc in clones.uniq) {
    curr.clone.sub <- clonal.data[which(clonal.data$j == uc), ]
    curr.clone.sub$new.cell.type <- paste0("Day_", curr.clone.sub$tp, "_", curr.clone.sub[,label.col])
    
    #d2.comp <- get.top.three.fates(curr.clone.sub[which(curr.clone.sub$tp == 2), ], "with.multi.label")
    d4.comp <- get.top.three.fates(curr.clone.sub[which(curr.clone.sub$tp == 4), ], "with.multi.label")
    d6.comp <- get.top.three.fates(curr.clone.sub[which(curr.clone.sub$tp == 6), ], "with.multi.label")    
    # if (nrow(d2.comp) >= 1) d2.comp$day <- "Day 2"
    if (nrow(d4.comp) >= 1) d4.comp$day <- "Day 4"
    if (nrow(d6.comp) >= 1) d6.comp$day <- "Day 6"
    curr.comp <- rbind(d6.comp, d4.comp)
    curr.comp$clone <- uc
    
    d4.subset <- curr.clone.sub[which(curr.clone.sub$tp == 4),]
    three.fate.filter <- get.top.three.fates(d4.subset, "with.multi.label")
    
    if (nrow(three.fate.filter) == 3) {
      comp.full <- rbind(rbind(curr.comp, curr.comp), curr.comp)
    } else if (nrow(three.fate.filter) == 2) {
      comp.full <- rbind(curr.comp, curr.comp)
    } else {
      comp.full <- curr.comp
    }
    
    comp.full$dominate.d4.fate <- rep(three.fate.filter$Var1, each = nrow(curr.comp))
    
    if (nrow(composition.clones) <= 0){
      composition.clones <- comp.full
    } else {
      composition.clones <- rbind(composition.clones, comp.full)
    }
  }
  
  return(composition.clones)
}
identify.strict.clones <- function(clonal.data, strict.label) {
  unique.clones <- unique(clonal.data$j)
  strict.d4.clone <- data.frame()
  for (uc in unique.clones) {
    curr.clone <- clonal.data[which(clonal.data$j == uc), ]
    curr.clone.d4 <- curr.clone[which(curr.clone$tp == 4),]
    if (nrow(curr.clone.d4) > 0) {
      cell.type.clone.d4 <- as.data.frame(sort(table(curr.clone.d4$with.multi.label), decreasing = T))
      if (nrow(cell.type.clone.d4) == 1) {
        if (rownames(cell.type.clone.d4) == strict.label) {
          strict.d4.clone <- c(strict.d4.clone, uc)
        }
      } 
      # else {
      #   if ((cell.type.clone.d4$Freq[1] >  cell.type.clone.d4$Freq[2]) & cell.type.clone.d4$Var1[1] == strict.label) {
      #     strict.d4.clone <- c(strict.d4.clone, uc)
      #   }
      # }
    }
  }
  return(strict.d4.clone)
}

Capybara

In general, for runtime consideration, we processed the dataset on a High Performance Computing resource. Here, we include the intermediate files, such as the reference, QP outcomes, and permutation results, in this folder for faster processing.

Construct the High-Resolution Reference

Here we construct the reference based on Day 6 major hematopoietic populations, including basophils, eosinophils, monocytes, neutrophils, and mast cells. This was executed on the computing cluster with 6 cores and 128G memory. Considering the long runtime, we will directly import the reference output from this script.

  1. Load the meta data
meta.larry <- read.table("~/Desktop/Reproducibility/Figure 3/Intermediates/LARRY State Fate Data/stateFate_inVitro_metadata.txt", sep = "\t", header = T, stringsAsFactors = F)
rownames(meta.larry) <- paste0("Cell_", seq(1,130887))
lsk <- meta.larry[which(meta.larry$Starting.population == "Lin-Kit+Sca1+"), ]
  1. Selection of proper cell types and construct references
### Only take Day 4 and Day 6 cells for reference
lsk.day.4.6 <- lsk[which(lsk$Time.point %in% c(4,6)), ]
lsk.day.4.6 <- lsk.day.4.6[which(lsk.day.4.6$Cell.type.annotation != "Undifferentiated"), ]

lsk.day.6 <- lsk[which(lsk$Time.point %in% c(6)), ]
lsk.day.6 <- lsk.day.6[which(lsk.day.6$Cell.type.annotation != "Undifferentiated"), ]

lsk.in.vitro <- readRDS("~/Desktop/Reproducibility/Figure 3/Intermediates/LARRY State Fate Data/lsk_matrix.Rds")
ct.freq.d6 <- as.data.frame(table(lsk.day.6$Cell.type.annotation))

## remove the cell type with less than 30 cells
less.than.30.ct.d6 <- as.character(ct.freq.d6[which(ct.freq.d6$Freq <= 30), "Var1"])

lsk.day.6.sub <- lsk.day.6[-which(lsk.day.6$Cell.type.annotation %in% less.than.30.ct.d6), ]
lsk.in.vitro <- as.matrix(t(lsk.in.vitro[rownames(lsk.day.4.6),]))

lsk.day.6.sub.wo.meg <- lsk.day.6.sub[which(lsk.day.6.sub$Cell.type.annotation != "Meg"), ]
lsk.day.6.sub.wo.meg.lym <- lsk.day.6.sub.wo.meg[which(lsk.day.6.sub.wo.meg$Cell.type.annotation != "Lymphoid"), ]

reference.new.d6.no.meg.lym <- construct.high.res.reference(lsk.in.vitro[,rownames(lsk.day.6.sub.wo.meg.lym)], lsk.day.6.sub.wo.meg.lym, criteria = "Cell.type.annotation")

saveRDS(reference.new.d6.no.meg.lym, "03_new_lsk_ref_with_cells_over_30_wo_undiff_d6_no_meg_lym.Rds")

Quadratic Programming

This was executed on the computing cluster with 4 cores and 200G memory. Considering the long runtime, we will directly import the QP output from this script.

  1. Read in the reference data and load into proper variables
ref.rslt.lsk <- readRDS("~/Desktop/Morris Lab/Manuscripts/Capybara/Revision/LARRY Rerun 102321/03_new_lsk_ref_with_cells_over_30_wo_undiff_d6_no_meg_lym.Rds")
ref.df.lsk <- ref.rslt.lsk[[3]]
ref.sc.lsk <- ref.rslt.lsk[[1]]
ref.meta.lsk <- ref.rslt.lsk[[2]]
  1. Prepare the data matrices for downstream analysis
lsk.in.vitro.undifferentiated <- lsk.in.vitro[rownames(lsk)[which(lsk$Cell.type.annotation == "Undifferentiated")], ]
lsk.in.vitro.undifferentiated <- as.matrix(t(lsk.in.vitro.undifferentiated))

lsk.in.vitro.differentiated <- lsk.in.vitro[rownames(lsk)[which(lsk$Cell.type.annotation != "Undifferentiated")], ]

lsk.in.vitro.differentiated.sub <- lsk.in.vitro.differentiated[setdiff(rownames(lsk.in.vitro.differentiated), ref.meta.lsk$cell.bc), ]
lsk.in.vitro.differentiated.sub <- as.matrix(t(lsk.in.vitro.differentiated.sub))
  1. Run Quadratic Programming
# Measure cell identity in the reference dataset as a background 
single.round.QP.analysis(ref.df.lsk, ref.sc.lsk, n.cores = 4, 
                          save.to.path = "/scratch/smlab/kongw/larry_rerun/", 
                          save.to.filename = "05_LSK_Based_LSK_Differentiated_Sample_Ref_no_meg_lym_no_force", unix.par = TRUE)

# Measure cell identity in the query dataset 
single.round.QP.analysis(ref.df.lsk, lsk.in.vitro.differentiated.sub, n.cores = 4, 
                         save.to.path = "/scratch/smlab/kongw/larry_rerun/", 
                         save.to.filename = "05_LSK_Based_LSK_Differentiated_Sample_Test_no_meg_lym_no_force", unix.par = TRUE)

# Measure cell identity in the query dataset 
single.round.QP.analysis(ref.df.lsk, lsk.in.vitro.undifferentiated, n.cores = 4, 
                         save.to.path = "/scratch/smlab/kongw/larry_rerun/", 
                         save.to.filename = "05_LSK_Based_LSK_Undifferentiated_Sample_Test_no_meg_lym_no_force", unix.par = TRUE)
  1. Here we load the pre-calculated QP results for this analysis.
## Background QP scores
ref.qp <- read.csv("~/Desktop/Reproducibility/Figure 3/Intermediates/QP_Outcomes/05_LSK_Based_LSK_Differentiated_Sample_Ref_no_meg_lym_no_force_scale.csv", header = T, row.names = 1, stringsAsFactors = F)

## QP scores for the undifferentiated cells
undifferentiated.qp <- read.csv("~/Desktop/Reproducibility/Figure 3/Intermediates/QP_Outcomes/05_LSK_Based_LSK_Undifferentiated_Sample_Test_no_meg_lym_no_force_scale.csv", header = T, row.names = 1, stringsAsFactors = F)

## QP scores for the differentiated cells
differentiated.qp <- read.csv("~/Desktop/Reproducibility/Figure 3/Intermediates/QP_Outcomes/05_LSK_Based_LSK_Differentiated_Sample_Test_no_meg_lym_no_force_scale.csv", header = T, row.names = 1, stringsAsFactors = F)

Empirical p-value calculation

To calculate the empirical p-value, run the following lines. Here we skip these lines to load previously obtained results.

col.sub <- ncol(ref.qp) - 2

# Conduct reference randomization to get empirical p-value matrix
ref.perc.list <- percentage.calc(as.matrix(ref.qp[,c(1:col.sub)]), as.matrix(ref.qp[,c(1:col.sub)]))

# Conduct test randomization to get empirical p-value matrix
diff.perc.list <- percentage.calc(as.matrix(differentiated.qp[,c(1:col.sub)]), as.matrix(ref.qp[,c(1:col.sub)]))
undiff.perc.list <- percentage.calc(as.matrix(undifferentiated.qp[,c(1:col.sub)]), as.matrix(ref.qp[,c(1:col.sub)]))

Here we load the previous empirical p-value data.

ref.perm.ls <- readRDS("~/Desktop/Morris Lab/Manuscripts/Capybara/Revision/LARRY Rerun 102321/05_ref_percent_list_new_ref_d6_differentiated_no_force.Rds")
perm.undiff.all <- readRDS("~/Desktop/Morris Lab/Manuscripts/Capybara/Revision/LARRY Rerun 102321/05_undiff_percent_list_new_ref_d6_differentiated_no_force.Rds")
perm.diff.all <- readRDS("~/Desktop/Morris Lab/Manuscripts/Capybara/Revision/LARRY Rerun 102321/05_diff_percent_list_new_ref_d6_differentiated_no_force.Rds")

Initial Classification

Here we perform initial classification based on quadratic programming metrics: deviance, error, and lagrangian multipliers. We mainly leverage deviance here to distinguish unknown cells from discrete cells and hybrid cells (note, we use the term ‘multi-id’ to refer to hybrid cells).

  1. Construct the ideal deviance distribution based on the background matrix
ideal.deviance <- ref.qp[,c(1:5)] - 1/5
ideal.deviance.all <- rowSums(abs(ideal.deviance))
ideal.deviance.all.mean <- mean(ideal.deviance.all)
ideal.deviance.sd <- sd(ideal.deviance.all)

fit <- fitdistr(ideal.deviance.all, "normal")

guessed.multi.id.deviance.mean <- ideal.deviance.all.mean - fit$estimate["sd"]
guessed.unknown.deviance.mean <- guessed.multi.id.deviance.mean - fit$estimate["sd"]

undifferentiated.qp.deviance <- abs(undifferentiated.qp[,c(1:5)] - 1/5)
differentiated.qp.deviance <- abs(differentiated.qp[,c(1:5)] - 1/5)

undifferentiated.qp.deviance$total.deviance <- rowSums(undifferentiated.qp.deviance)
differentiated.qp.deviance$total.deviance <- rowSums(differentiated.qp.deviance)

undifferentiated.qp$deviance <- undifferentiated.qp.deviance[rownames(undifferentiated.qp), "total.deviance"]
differentiated.qp$deviance <- differentiated.qp.deviance[rownames(differentiated.qp), "total.deviance"]

## Calculate p-values (Undifferentiated Cells)
undifferentiated.qp$deviance.p <- pnorm(undifferentiated.qp$deviance, mean = ideal.deviance.all.mean, sd = ideal.deviance.sd, lower.tail = T)
undifferentiated.qp$deviance.p.multi <- pnorm(undifferentiated.qp$deviance, mean = guessed.multi.id.deviance.mean, sd = ideal.deviance.sd/2, lower.tail = T)
undifferentiated.qp$deviance.p.unknown <- pnorm(undifferentiated.qp$deviance, mean = guessed.unknown.deviance.mean, sd = ideal.deviance.sd, lower.tail = T)

## Calculate p-values (Differetiated Cells)
differentiated.qp$deviance.p <- pnorm(differentiated.qp$deviance, mean = ideal.deviance.all.mean, sd = ideal.deviance.sd, lower.tail = T)
differentiated.qp$deviance.p.multi <- pnorm(differentiated.qp$deviance, mean = guessed.multi.id.deviance.mean, sd = ideal.deviance.sd/2, lower.tail = T)
differentiated.qp$deviance.p.unknown <- pnorm(differentiated.qp$deviance, mean = guessed.unknown.deviance.mean, sd = ideal.deviance.sd, lower.tail = T)
  1. Threshold selection
plot(undifferentiated.qp$deviance.p.multi, undifferentiated.qp$deviance.p)
abline(v = 0.05, col = "red")
abline(h = 0.05, col = "blue")


plot(undifferentiated.qp$deviance.p.unknown, undifferentiated.qp$deviance.p.multi)
abline(h = 0.05, col = "blue")


plot(differentiated.qp$deviance.p.multi, differentiated.qp$deviance.p)
abline(v = 0.05, col = "red")
abline(h = 0.05, col = "blue")


plot(differentiated.qp$deviance.p.unknown, differentiated.qp$deviance.p.multi)
abline(h = 0.05, col = "blue")

  1. Create the initial classification data frame
## initial class data frame for the data (Undifferentiated)
init.class.undiff <- data.frame(cell.bc = rownames(undifferentiated.qp), init.class = "Single-ID", stringsAsFactors = F)
rownames(init.class.undiff) <- init.class.undiff$cell.bc
init.class.undiff[rownames(undifferentiated.qp[which(undifferentiated.qp$deviance.p >= 0.05 & undifferentiated.qp$deviance.p.multi >= 0.95), ]), "init.class"] <- "Single-ID"
init.class.undiff[rownames(undifferentiated.qp[which(undifferentiated.qp$deviance.p.multi >= 0 & undifferentiated.qp$deviance.p.unknown > 0.95 & undifferentiated.qp$deviance.p < 0.05), ]), "init.class"] <- "Multi_ID"
init.class.undiff[rownames(undifferentiated.qp[which(undifferentiated.qp$deviance.p.multi < 0.05 & undifferentiated.qp$deviance.p.unknown >= 0), ]), "init.class"] <- "Unknown"

## initial class data frame for the data (Differentiated)
init.class.diff <- data.frame(cell.bc = rownames(differentiated.qp), init.class = "Single-ID", stringsAsFactors = F)
rownames(init.class.diff) <- init.class.diff$cell.bc
init.class.diff[rownames(differentiated.qp[which(differentiated.qp$deviance.p >= 0.05 & differentiated.qp$deviance.p.multi >= 0.95), ]), "init.class"] <- "Single-ID"
init.class.diff[rownames(differentiated.qp[which(differentiated.qp$deviance.p.multi >= 0 & differentiated.qp$deviance.p.unknown > 0.95 & differentiated.qp$deviance.p < 0.05), ]), "init.class"] <- "Multi_ID"
init.class.diff[rownames(differentiated.qp[which(differentiated.qp$deviance.p.multi < 0.05 & differentiated.qp$deviance.p.unknown > 0), ]), "init.class"] <- "Unknown"

Binarization and Classification

We generate the binarization matrix so that unknowns are labelled ‘0’ and known cell types are labelled ‘1’, and perform classification based on the binarized count. Due to the large cell number, we directly load the binarization counts and classifications.

  1. Binarization
col.sub <- ncol(ref.qp) - 2
bin.count.undiff <- binarization.mann.whitney(mtx = undifferentiated.qp[,c(1:col.sub)], 
                                            ref.perc.ls = ref.perm.ls[[1]], 
                                            ref.meta = ref.meta.lsk, perc.ls = perm.undiff.all[[1]], 
                                            init.class = init.class.undiff)

bin.count.diff <- binarization.mann.whitney(mtx = differentiated.qp[,c(1:col.sub)], 
                                            ref.perc.ls = ref.perm.ls[[1]], 
                                            ref.meta = ref.meta.lsk, perc.ls = perm.diff.all[[1]],
                                            init.class = init.class.diff)
col.sub <- ncol(ref.qp) - 2
bin.count.undiff <- readRDS("~/Desktop/Reproducibility/Figure 3/Intermediates/Binary_Counts_and_Classifications/undifferentiated_binary_counts.Rds")
bin.count.diff <- readRDS("~/Desktop/Reproducibility/Figure 3/Intermediates/Binary_Counts_and_Classifications/differentiated_binary_counts.Rds")
  1. Classification
classification.undiff <- binary.to.classification(bin.count.undiff[,c(1:col.sub)])
rownames(classification.undiff) <- classification.undiff$barcode
classification.diff <- binary.to.classification(bin.count.diff[,c(1:col.sub)])
rownames(classification.diff) <- classification.diff$barcode
classification.undiff <- readRDS("~/Desktop/Reproducibility/Figure 3/Intermediates/Binary_Counts_and_Classifications/Undifferentiated_class.Rds")
classification.diff <- readRDS("~/Desktop/Reproducibility/Figure 3/Intermediates/Binary_Counts_and_Classifications/Differentiated_class.Rds")

To this end, we have finished classification of the cells in this dataset. Next, we check the classification result against the cell type annotation from LARRY to check the accuracy of classification and fidelity of the reference.

Classification Check

  1. We put the actual label in the classification data frame and compare.
classification.undiff$actual <- lsk[rownames(classification.undiff), "Cell.type.annotation"]
classification.undiff$timepoint <- meta.larry[rownames(classification.undiff), "Time.point"]
table(classification.undiff$call, classification.undiff$timepoint)
            
                 2     4     6
  Baso           0    14    43
  Eos            0    10    28
  Mast          26    92   187
  Monocyte     124   590   529
  Multi_ID       3    17    30
  Neutrophil   116   767   922
  Unknown    19654 21182 10015
classification.diff$actual <- lsk[rownames(classification.diff), "Cell.type.annotation"]
classification.diff$timepoint <- meta.larry[rownames(classification.diff), "Time.point"]
table(classification.diff$call, classification.diff$timepoint)
            
                2    4    6
  Baso         40  607 1291
  Eos           2   34    8
  Mast         10  187  149
  Monocyte    141 3689 5631
  Multi_ID      0    9   10
  Neutrophil   86  977 2686
  Unknown     254 1276 1115
table(classification.diff$call, classification.diff$actual)
            
             Baso Ccr7_DC  Eos Erythroid Lymphoid Mast  Meg Monocyte Neutrophil  pDC
  Baso       1937       0    0         0        0    1    0        0          0    0
  Eos           6       0   38         0        0    0    0        0          0    0
  Mast          5       0    0         0        0  337    4        0          0    0
  Monocyte      1      43    0         0        0    3    0     9406          4    4
  Multi_ID     10       0    2         0        0    3    0        3          1    0
  Neutrophil    1       0    0         0        1    0    1        9       3737    0
  Unknown     824      95    2        32      766    5  158      523        202   38
  1. We plot the new classification for the undifferentiated cells
lsk.undiff <- lsk[which(lsk$Cell.type.annotation == "Undifferentiated"),]
lsk.undiff$capy.call <- classification.undiff[rownames(lsk.undiff), "call"]

lsk.diff <- lsk[which(lsk$Cell.type.annotation != "Undifferentiated"),]
lsk.diff$capy.call <- classification.diff[rownames(lsk.diff), "call"]

ggplot(lsk, aes(x = SPRING.x, y = SPRING.y)) +
  geom_point(color = "grey") +
  geom_point(data = lsk.undiff[which(lsk.undiff$capy.call %in% c("Multi_ID")),], mapping = aes(x = SPRING.x, y = SPRING.y, color = capy.call))


ggplot(lsk, aes(x = SPRING.x, y = SPRING.y)) +
  geom_point(color = "grey") +
  geom_point(data = lsk.undiff[which(lsk.undiff$capy.call %in% c("Unknown")),], mapping = aes(x = SPRING.x, y = SPRING.y, color = capy.call))


ggplot(lsk, aes(x = SPRING.x, y = SPRING.y)) +
  geom_point(color = "grey") +
  geom_point(data = lsk.undiff[-which(lsk.undiff$capy.call %in% c("Erythroid", "Lymphoid", "Multi_ID", "Unknown")),], mapping = aes(x = SPRING.x, y = SPRING.y, color = capy.call))

  1. We plot the new classification for the differentiated cells.
ggplot(lsk, aes(x = SPRING.x, y = SPRING.y)) +
  geom_point(color = "grey") +
  geom_point(data = lsk.diff[which(lsk.diff$capy.call %in% c("Multi_ID")),], mapping = aes(x = SPRING.x, y = SPRING.y, color = capy.call))


ggplot(lsk, aes(x = SPRING.x, y = SPRING.y)) +
  geom_point(color = "grey") +
  geom_point(data = lsk.diff[which(lsk.diff$capy.call %in% c("Unknown")),], mapping = aes(x = SPRING.x, y = SPRING.y, color = capy.call))


ggplot(lsk, aes(x = SPRING.x, y = SPRING.y)) +
  geom_point(color = "grey") +
  geom_point(data = lsk.diff[-which(lsk.diff$capy.call %in% c("Erythroid", "Lymphoid", "Multi_ID", "Unknown")),], mapping = aes(x = SPRING.x, y = SPRING.y, color = capy.call))

  1. We compare our annotation of the differentiated cells to the cell type annotation from LARRY via heatmap. The color of each block represents percentages. Higher percentage = lighter color.
heatmap.to.plot <- as.data.frame(apply(table(classification.diff$call, classification.diff$actual), 2, function(x) round(x*100/sum(x), digits = 3)))

heatmap.to.plot$capy.call <- rownames(heatmap.to.plot)
heatmap.to.plot.melt <- reshape2::melt(heatmap.to.plot)
Using capy.call as id variables
heatmap.to.plot.melt$capy.call <- factor(heatmap.to.plot.melt$capy.call,
                                         levels = c("Baso", "Eos", "Mast", "Monocyte", "Neutrophil", "Unknown", "Multi_ID"),
                                         ordered = T)
heatmap.to.plot.melt$variable <- factor(heatmap.to.plot.melt$variable,
                                         levels = c("Baso", "Eos", "Mast", "Monocyte", "Neutrophil", "Meg", "Erythroid",
                                                    "Lymphoid", "pDC", "Ccr7_DC"),
                                         ordered = T)
ggplot(heatmap.to.plot.melt, aes(x = capy.call, y = variable, fill = value)) +
  geom_tile() +
  scale_fill_viridis_c(option = "A", begin = 0.15, end = 0.85) +
  theme(legend.position = "right",
        axis.text.x = element_text(face = "bold", size = 12, angle = 45, hjust = 1),
        axis.text.y = element_text(face = "bold", size = 12),
        axis.title.x = element_text(face = "bold.italic", size = 14),
        axis.title.y = element_text(face = "bold.italic", size = 14),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        panel.background = element_blank(), 
        title = element_text(face = "bold.italic", size = 14),
        axis.line = element_line(colour = "black"),
        axis.ticks = element_blank())

To this end, we have checked the accuracy of classification. Specifically, cell types that did not contribute to reference making mainly contribute to the unknown population in the classification of differentiated cells. We next check the distribution of the hybrid cells before moving into assessing lineage.

Hybrid cells

For further downstream analysis, we reformated the hybrid cells and put them into a data frame with the detailed discrete representations of the hybrids.

  1. Here we first put the detailed breakdowns of the hybrid cells into a data frame
all.bin.count <- rbind(bin.count.diff, bin.count.undiff)
all.classification <- rbind(classification.diff, classification.undiff)
multi.id.cells <- all.classification$barcode[which(all.classification$call == "Multi_ID")]

binary.m.id <- as.data.frame(all.bin.count[multi.id.cells, ])
binary.m.id$cell.id <- rownames(binary.m.id)
binary.m.id.melt <- reshape2::melt(binary.m.id)
Using cell.id as id variables
binary.m.id.melt <- binary.m.id.melt[which(binary.m.id.melt$value > 0), ]

binary.new.2 <- data.frame()
binary.m.id.melt$variable <- as.character(binary.m.id.melt$variable)
for (mc in multi.id.cells) {
  curr.sub <- binary.m.id.melt[which(binary.m.id.melt$cell.id == mc), ]
  curr.cell.type.combine <- paste0(gsub(pattern = "frxn_cell.type_", replacement = "", x = curr.sub$variable), collapse = "-")
  
  curr.df <- data.frame(cell = mc, cell.type = curr.cell.type.combine, stringsAsFactors = F)
  if (nrow(binary.new.2) <= 0){
    binary.new.2 <- curr.df
  } else {
    binary.new.2 <- rbind(binary.new.2, curr.df)
  }
}
binary.new.2.d2 <- binary.new.2
rownames(binary.new.2.d2) <- binary.new.2.d2$cell
binary.new.2.d2$tp <- lsk[rownames(binary.new.2.d2), "Time.point"]
  1. We assess the major hybrid populations here.
hybrid.freq.table <- as.data.frame(table(binary.new.2.d2$cell.type) * 100/sum(table(binary.new.2.d2$cell.type)))
hybrid.freq.table.sort <- hybrid.freq.table[order(-hybrid.freq.table$Freq), ]

hybrid.freq.table.sort$Var1 <- factor(hybrid.freq.table.sort$Var1, levels = hybrid.freq.table.sort$Var1, ordered = T)

ggplot(hybrid.freq.table.sort, aes(x = Var1, y = Freq, fill = Var1)) +
  geom_bar(position = "dodge", stat = "identity") +
  scale_fill_viridis_d(option = "A", begin = 0.15, end = 0.85) +
  theme(legend.position = "none",
        axis.text.x = element_text(face = "bold", size = 12, angle = 90, hjust = 1),
        axis.text.y = element_text(face = "bold", size = 12),
        axis.title.x = element_blank(),
        axis.title.y = element_text(face = "bold.italic", size = 14),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        panel.background = element_blank(), 
        title = element_text(face = "bold.italic", size = 14),
        axis.line = element_line(colour = "black"),
        axis.ticks.x = element_blank())

Overall, we identified the major hybrid populations. We investigate the potential of these hybrid cells using the lineage tracing data.

Clonal information

Here we load the clonal information from Weinreb et al. and preprocess it in the proper format. We identify the state-fate clones and state-state clones.

  1. Load clonal information & identify state-fate clones and state-state clones
library(Matrix)
## read in clone matrix and reform the matrix to clone by cell
clone.anno<-readMM("~/Desktop/Reproducibility/Figure 3/Intermediates/LARRY State Fate Data/stateFate_inVitro_clone_matrix.mtx")
clone.anno<-t(clone.anno)
## read in meta
meta<-read.table("~/Desktop/Reproducibility/Figure 3/Intermediates/LARRY State Fate Data/stateFate_inVitro_metadata.txt",header = T,sep="\t")
## for each clone, find cell index
meta.cell.ind<-apply(clone.anno,1, function(x) which(x!=0))
## find multi-cell clones
multi.cell.clone<-sapply(meta.cell.ind, function(x) length(x)>1)
## find cell index for each multi-cell clone
multi.cell.ind<-apply(clone.anno[which(multi.cell.clone==T),],1, function(x) which(x!=0))
## find dev time for cells
clone.time<-sapply(multi.cell.ind, function(x) meta$Time.point[x])

## find state-fate clones
state.fate.clone<-sapply(clone.time, function(x) length(unique(x))!=1&&2%in%unique(x))
state.fate.clone.index<-which(state.fate.clone==T)

## find state-state clones
state.state.clone<-sapply(clone.time, function(x) sum(x==2)>=2)
state.state.clone.index<-which(state.state.clone==T)
  1. Further formatting of the data frame and add time point information
clone.anno<-readMM("~/Desktop/Reproducibility/Figure 3/Intermediates/LARRY State Fate Data/stateFate_inVitro_clone_matrix.mtx")
rownames(clone.anno)<-paste("Cell_",1:130887,sep = "")
colnames(clone.anno)<-paste("Clone_",1:5864,sep = "")

rownames(meta) <- paste("Cell_",1:130887,sep = "")

clone.anno.state.fate <- as.data.frame(Matrix::summary(clone.anno[, state.fate.clone.index]))
clone.anno.state.state <- as.data.frame(Matrix::summary(clone.anno[, state.state.clone.index]))

clone.anno.state.fate$i <- paste("Cell_", clone.anno.state.fate$i, sep = "")
clone.anno.state.fate$j <- paste("Clone_", clone.anno.state.fate$j, sep = "")

clone.anno.state.state$i <- paste("Cell_", clone.anno.state.state$i, sep = "")
clone.anno.state.state$j <- paste("Clone_", clone.anno.state.state$j, sep = "")

clone.anno.state.fate$tp <- meta[clone.anno.state.fate$i, "Time.point"]
clone.anno.state.state$tp <- meta[clone.anno.state.state$i, "Time.point"]
  1. Add the classification information into the clonal data and add the cell type annotation from LARRY into the clonal data frame
rownames(clone.anno.state.fate) <- clone.anno.state.fate$i
rownames(clone.anno.state.state) <- clone.anno.state.state$i

clone.anno.state.fate$capy.call <- NA

full.classification.table <- rbind(classification.undiff, classification.diff)
full.class.intersect.sf <- intersect(rownames(full.classification.table), rownames(clone.anno.state.fate))
full.class.intersect.ss <- intersect(rownames(full.classification.table), rownames(clone.anno.state.state))

clone.anno.state.fate[full.class.intersect.sf, "capy.call"] <- full.classification.table[full.class.intersect.sf, "call"]
clone.anno.state.state[full.class.intersect.ss, "capy.call"] <- full.classification.table[full.class.intersect.ss, "call"]

clone.anno.state.fate.no.na <- clone.anno.state.fate[!is.na(clone.anno.state.fate$capy.call), ]
clone.anno.state.state.no.na <- clone.anno.state.state[!is.na(clone.anno.state.state$capy.call), ]

clone.anno.state.state.no.na$actual.call <- lsk[rownames(clone.anno.state.state.no.na), "Cell.type.annotation"]
clone.anno.state.fate.no.na$actual.call <- lsk[rownames(clone.anno.state.fate.no.na), "Cell.type.annotation"]
  1. Fill in the detailed breakdowns of the hybrid cells
multi_id.sf.clones <- unique(clone.anno.state.fate.no.na$j[which(clone.anno.state.fate.no.na$capy.call == "Multi_ID")])

clone.anno.state.fate.no.na[, "with.multi.label"] <- binary.new.2.d2[clone.anno.state.fate.no.na$i, "cell.type"]
clone.anno.state.fate.no.na[which(is.na(clone.anno.state.fate.no.na$with.multi.label)), "with.multi.label"] <- clone.anno.state.fate.no.na$capy.call[which(is.na(clone.anno.state.fate.no.na$with.multi.label))]
  1. Assess the lineage related siblings of the hybrid cells. We first compute the clonal composition of the clones where the hybrid cells reside in a data frame.
clones.sub.with.no.unknown <- clone.anno.state.fate.no.na[which(clone.anno.state.fate.no.na$with.multi.label != "Unknown"), ]
clones.ss.sub.with.no.unknown <- clone.anno.state.state.no.na[which(clone.anno.state.state.no.na$capy.call != "Unknown"), ]
clones.ss.sub.with.no.unknown$with.multi.label <- clones.ss.sub.with.no.unknown$capy.call
clones.ss.sub.with.no.unknown$j <- paste0("ss_", clones.ss.sub.with.no.unknown$j)
clones.ss.sub.with.no.unknown[intersect(rownames(clones.ss.sub.with.no.unknown), rownames(binary.new.2.d2)), "with.multi.label"] <- binary.new.2.d2[intersect(rownames(clones.ss.sub.with.no.unknown), rownames(binary.new.2.d2)), "cell.type"]

clones.aggr <- rbind(clones.sub.with.no.unknown, clones.ss.sub.with.no.unknown)
meg.neutro <- compute.composition.clones(clones.aggr, "with.multi.label")

clones.with.multi <- meg.neutro[which(meg.neutro$Var1 %in% c("Day_4_Monocyte-Neutrophil", "Day_6_Monocyte-Neutrophil",
                                                             "Day_4_Baso-Mast", "Day_6_Baso-Eos")), ]
rownames(clones.with.multi) <- as.character(clones.with.multi$clone)
clones.with.multi$Var1 <- as.character(clones.with.multi$Var1)

meg.neutro.sub <- meg.neutro[which(meg.neutro$clone %in% clones.with.multi$clone), ]

meg.neutro.sub$Var1 <- as.character(meg.neutro.sub$Var1)
meg.neutro.sub$new.var <- unlist(lapply(strsplit(meg.neutro.sub$Var1, "_"), function(x) paste0(x[3:length(x)], collapse = "-")))

meg.neutro.sub$clone.multi.label <- clones.with.multi[meg.neutro.sub$clone, "Var1"]
meg.neutro.sub$clone.multi.label <- gsub("Day_4_", "", meg.neutro.sub$clone.multi.label)
meg.neutro.sub$clone.multi.label <- gsub("Day_6_", "", meg.neutro.sub$clone.multi.label)

meg.neutro.dt <- setDT(meg.neutro.sub)
meg.neutro.mtx <- dcast(meg.neutro.dt, clone.multi.label~new.var, fill = 0, value.var = "Freq", fun.aggregate = mean)
meg.neutro.mtx.recollapse <- reshape2::melt(meg.neutro.mtx)
Using clone.multi.label as id variables
  1. We next plot the percentage of the counter parts of these hybrid cells.
meg.neutro.mtx.recollapse$variable <- as.character(meg.neutro.mtx.recollapse$variable)

meg.neutro.mtx.recollapse$variable <- factor(meg.neutro.mtx.recollapse$variable,
                                             levels = c("Baso", "Eos", "Mast", "Monocyte", "Neutrophil",
                                                        "Baso-Mast", "Monocyte-Neutrophil", "Baso-Eos"),
                                             ordered = T)

meg.neutro.mtx.recollapse$clone.multi.label <- factor(meg.neutro.mtx.recollapse$clone.multi.label,
                                             levels = c("Baso-Eos", "Baso-Mast", "Monocyte-Neutrophil"),
                                             ordered = T)

ggplot(meg.neutro.mtx.recollapse, aes(x = variable, y = clone.multi.label, size = ifelse(value==0, NA, value), color = variable, fill = variable)) +
  geom_point() +
  scale_color_viridis_d(option = "A", begin = 0.15, end = 0.85) +
  scale_fill_viridis_d(option = "A", begin = 0.15, end = 0.85) +
  scale_size_area(name = "percentage", max_size = 10)+
  labs(y = "Hybrid Identities", x = "Clonal Identities") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 12, face = "bold.italic"),
        legend.position = "none",
        axis.text.y = element_text(size = 12, face = "bold.italic"))
Warning: Removed 13 rows containing missing values (geom_point).

  1. Here we are trying to find the clonally related cells to the strict defined day 4 clones. For instance, we are searching for the siblings for day 4 clones that are 100% composed of Monocytes to look at their lineage restriction on day 6.
#sf.ori <- clone.anno.state.fate.no.na
clone.anno.state.fate.no.na <- rbind(clone.anno.state.fate.no.na, clones.ss.sub.with.no.unknown)
# clone.anno.state.fate.no.na <- clone.anno.state.fate.no.na[which(clone.anno.state.fate.no.na$with.multi.label != "Unknown"),]
d4.d6.clonal.info <- clone.anno.state.fate.no.na[which(clone.anno.state.fate.no.na$tp %in% c(4,6)), ]
labels <- unique(d4.d6.clonal.info$with.multi.label)
single.labels <- unlist(lapply(strsplit(labels, "-"), function(x) length(x) == 1))
single.labels.chr <- labels[single.labels]

strict.clone.compositions <- data.frame()
strict.clones.all <- list()

for (curr.l in single.labels.chr) {
  strict.clones <- unlist(identify.strict.clones(d4.d6.clonal.info, curr.l))
  d4.strict <- clone.anno.state.fate.no.na[which(clone.anno.state.fate.no.na$j %in% strict.clones),]
  d4.clone.size <- table(d4.strict$j)
  
  d4.strict <- d4.strict[which(d4.strict$j %in% names(which(d4.clone.size > 3))), ]
  
  if (nrow(d4.strict) > 0) {
    curr.strict.d4 <- d4.strict[which(d4.strict$tp == 4), ]
    curr.strict.d6 <- d4.strict[which(d4.strict$tp == 6), ]
    
    strict.clones.all[[curr.l]] <- unique(d4.strict$j)
    
    if (nrow(curr.strict.d6) > 0) {
  
      curr.strict.top.dominant.freq.d4 <- as.data.frame(table(curr.strict.d4$with.multi.label) * 100/sum(table(curr.strict.d4$with.multi.label)))
      d6.top.fates <- as.character(unique(curr.strict.d6$with.multi.label)[which(unique(curr.strict.d6$with.multi.label) != "Unknown")])
      curr.strict.top.dominant.freq.d6 <- curr.strict.d6[which(curr.strict.d6$with.multi.label %in% d6.top.fates),]
      d6.domin.comp <- as.data.frame(table(curr.strict.top.dominant.freq.d6$with.multi.label) * 100/nrow(curr.strict.top.dominant.freq.d6))
      curr.strict.top.dominant.freq.d4$d4.label <- curr.l
      d6.domin.comp$d4.label <- curr.l
      curr.strict.top.dominant.freq.d4$day <- "Day 4"
      d6.domin.comp$day <- "Day 6"
      
      curr.strict.top.dominant.freq <- rbind(curr.strict.top.dominant.freq.d4, d6.domin.comp)
      
      if (nrow(strict.clone.compositions) <= 0) {
        strict.clone.compositions <- curr.strict.top.dominant.freq
      } else {
        strict.clone.compositions <- rbind(strict.clone.compositions, curr.strict.top.dominant.freq)
      }
    }
  }
}

strict.clone.compositions$new.label <- paste0(strict.clone.compositions$day, "_", strict.clone.compositions$Var1)
dt <- setDT(strict.clone.compositions)
dt.mtx <- dcast(dt, d4.label~new.label, fill = 0, value.var = "Freq", fun.aggregate = mean)

dt.mtx.recollapse <- reshape2::melt(dt.mtx)
Using d4.label as id variables
  1. Here we are trying to find the clonally related cells to the hybrid cells defined on Day 4, particularly in this case, testing monocyte-neutrophil hybrids and basophil-mast cell hybrids.
single.recollapse <- dt.mtx.recollapse

double.labels <- unlist(lapply(strsplit(labels, "-"), function(x) length(x) == 2))
double.labels.chr <- labels[double.labels]

dl.clone.compositions <- data.frame()
dl.clones.all <- list()

for (curr.dl in double.labels.chr) {
  clones.with.curr.dl <- unique(clone.anno.state.fate.no.na$j[which(clone.anno.state.fate.no.na$with.multi.label == curr.dl)])
  d4.strict <- clone.anno.state.fate.no.na[which(clone.anno.state.fate.no.na$j %in% clones.with.curr.dl),]
  
  d4.clone.size <- table(d4.strict$j)
  
  d4.strict <- d4.strict[which(d4.strict$j %in% names(which(d4.clone.size >= 3))), ]
  
  if (nrow(d4.strict) > 0) {
    
    curr.strict.d4 <- d4.strict[which(d4.strict$tp == 4), ]
    curr.strict.d6 <- d4.strict[which(d4.strict$tp == 6), ]
    
    dl.clones.all[[curr.dl]] <- unique(d4.strict$j)
    if (nrow(curr.strict.d4) == 0) next
    
    if (nrow(curr.strict.d6) > 0) {
  
      curr.strict.top.dominant.freq.d4 <- as.data.frame(table(curr.strict.d4[which(curr.strict.d4$with.multi.label != "Unknown"), "with.multi.label"]) * 100/(sum(table(curr.strict.d4[which(curr.strict.d4$with.multi.label != "Unknown"), "with.multi.label"]))))
      d6.top.fates <- as.character(unique(curr.strict.d6$with.multi.label)[which(unique(curr.strict.d6$with.multi.label) != "Unknown")])
      curr.strict.top.dominant.freq.d6 <- curr.strict.d6[which(curr.strict.d6$with.multi.label %in% d6.top.fates),]
      d6.domin.comp <- as.data.frame(table(curr.strict.top.dominant.freq.d6$with.multi.label) * 100/nrow(curr.strict.top.dominant.freq.d6))
      curr.strict.top.dominant.freq.d4$d4.label <- curr.dl
      d6.domin.comp$d4.label <- curr.dl
      curr.strict.top.dominant.freq.d4$day <- "Day 4"
      d6.domin.comp$day <- "Day 6"
      
      curr.strict.top.dominant.freq <- rbind(curr.strict.top.dominant.freq.d4, d6.domin.comp)
      
      if (nrow(dl.clone.compositions) <= 0) {
        dl.clone.compositions <- curr.strict.top.dominant.freq
      } else {
        dl.clone.compositions <- rbind(dl.clone.compositions, curr.strict.top.dominant.freq)
      }
  }
  }
}

dl.clone.compositions$new.label <- paste0(dl.clone.compositions$day, "_", dl.clone.compositions$Var1)
dt <- setDT(dl.clone.compositions)
dt.mtx <- dcast(dt, d4.label~new.label, fill = 0, value.var = "Freq", fun.aggregate = mean)

dt.mtx.recollapse <- reshape2::melt(dt.mtx)
Using d4.label as id variables
  1. We combine the strict clone data and the hybrid clonal data.
dt.mtx.recollapse$variable <- as.character(dt.mtx.recollapse$variable)
dt.mtx.recollapse$ct <- unlist(lapply(strsplit(dt.mtx.recollapse$variable, "_"), function(x) x[length(x)]))
dt.mtx.recollapse$tp <- unlist(lapply(strsplit(dt.mtx.recollapse$variable, "_"), function(x) x[1]))

single.recollapse$variable <- as.character(single.recollapse$variable)
single.recollapse$ct <- unlist(lapply(strsplit(single.recollapse$variable, "_"), function(x) x[length(x)]))
single.recollapse$tp <- unlist(lapply(strsplit(single.recollapse$variable, "_"), function(x) x[1]))

dt.mtx.recollapse.sub <- dt.mtx.recollapse[which(dt.mtx.recollapse$d4.label %in% c("Monocyte-Neutrophil", "Baso-Eos", "Baso-Mast")), ]

single.recollapse.sub <- single.recollapse[which(single.recollapse$d4.label != "Unknown"), ]
single.double.recollapse <- rbind(single.recollapse.sub, dt.mtx.recollapse.sub)
  1. We take a look at these hybrid clones, compared to their discrete counter parts.
  1. Monocyte-Neutrophil
subset.mono.neutro <- single.double.recollapse[which(single.double.recollapse$d4.label %in% c("Monocyte", "Neutrophil", "Monocyte-Neutrophil")), ]

subset.mono.neutro <- subset.mono.neutro[-which(subset.mono.neutro$value == 0),]

subset.mono.neutro$ct <- factor(subset.mono.neutro$ct,
                                levels = c("Monocyte", "Neutrophil", "Monocyte-Neutrophil", "Baso", "Mast", "Eos", "Baso-Eos", "Unknown"),
                                ordered = T)
subset.mono.neutro$d4.label <- factor(subset.mono.neutro$d4.label,
                                levels = rev(c("Monocyte", "Neutrophil", "Monocyte-Neutrophil")),
                                ordered = T)
ggplot(subset.mono.neutro, aes(x = ct, y = d4.label, size = ifelse(value==0, NA, value), color = ct, fill = ct)) +
  geom_point() +
  scale_color_viridis_d(option = "A", begin = 0.15, end = 0.85) +
  scale_fill_viridis_d(option = "A", begin = 0.15, end = 0.85) +
  scale_size_area(name = "percentage", max_size = 10)+
  facet_grid(.~tp) +
  theme(axis.text.x = element_text(angle = 90, size = 12, face = "bold.italic"),
        legend.position = "right",
        axis.text.y = element_text(size = 12, face = "bold.italic"))

  1. Basophil-Mast
subset.baso.mast <- single.double.recollapse[which(single.double.recollapse$d4.label %in% c("Baso", "Mast", "Baso-Mast")), ]
subset.baso.mast <- subset.baso.mast[-which(subset.baso.mast$value == 0), ]

subset.baso.mast$ct <- factor(subset.baso.mast$ct,
                                levels = c("Baso", "Mast", "Baso-Mast", "Monocyte", "Neutrophil"),
                                ordered = T)
subset.baso.mast$d4.label <- factor(subset.baso.mast$d4.label,
                                levels = rev(c("Baso", "Mast", "Baso-Mast")),
                                ordered = T)
ggplot(subset.baso.mast, aes(x = ct, y = d4.label, size = ifelse(value==0, NA, value), color = ct, fill = ct)) +
  geom_point() +
  scale_color_viridis_d(option = "A", begin = 0.15, end = 0.85) +
  scale_fill_viridis_d(option = "A", begin = 0.15, end = 0.85) +
  scale_size_area(name = "percentage", max_size = 10)+
  facet_grid(.~tp) +
  theme(axis.text.x = element_text(angle = 90, size = 12, face = "bold.italic"),
        legend.position = "right",
        axis.text.y = element_text(size = 12, face = "bold.italic"))

  1. Here we take a closer look at the monocyte-neutrophil hybrids. Again, we first look at the lineage restricted day 4 clones and their correlated day 6 siblings. Then we look at the hybrids.
unique.clones <- unique(clone.anno.state.fate.no.na$j)
monocyte.clones.d4 <- c()
neutro.clones.d4 <- c()
for (uc in unique.clones) {
  curr.clone <- clone.anno.state.fate.no.na[which(clone.anno.state.fate.no.na$j == uc), ]
  curr.clone.d4 <- curr.clone[which(curr.clone$tp == 4),]
  if (nrow(curr.clone.d4) > 0) {
    cell.type.clone.d4 <- unique(curr.clone.d4$with.multi.label)
    if (length(cell.type.clone.d4) == 1) {
      if (cell.type.clone.d4 == "Monocyte") {
        monocyte.clones.d4 <- c(monocyte.clones.d4, uc)
      }
      if (cell.type.clone.d4 == "Neutrophil") {
        neutro.clones.d4 <- c(neutro.clones.d4, uc)
      }
    }
  }
}

mono.d4.strict <- clone.anno.state.fate.no.na[which(clone.anno.state.fate.no.na$j %in% strict.clones.all[["Monocyte"]]),]
neutro.d4.strict <- clone.anno.state.fate.no.na[which(clone.anno.state.fate.no.na$j %in% strict.clones.all[["Neutrophil"]]),]
mono.neutro.d4.clone <- clone.anno.state.fate.no.na[which(clone.anno.state.fate.no.na$j %in% dl.clones.all[["Monocyte-Neutrophil"]]),]

mono.d6.strict <- mono.d4.strict[which(mono.d4.strict$tp %in% c(4,6)), ]
neutro.d6.strict <- neutro.d4.strict[which(neutro.d4.strict$tp %in% c(4,6)), ]
mono.neutro.d6.strict <- mono.neutro.d4.clone[which(mono.neutro.d4.clone$tp %in% c(4,6)), ]

mono.neutro.d6.strict.dom.fate <- compute.composition.clones(clonal.data = mono.neutro.d6.strict, "with.multi.label")

d6.cells.singles.freq <- as.data.frame(table(mono.d6.strict$with.multi.label) * 100/nrow(mono.d6.strict))
  1. Neutrophil strict clones
d6.cells.singles.freq <- as.data.frame(table(neutro.d6.strict$with.multi.label) * 100/nrow(neutro.d6.strict))
plot.bar.chart(d6.cells.singles.freq, "~/Desktop/larry_d4_d6_neutro_bar.pdf", 5, 7, "d6 siblings")

  1. Monocyte strict clones
d6.cells.singles.freq <- as.data.frame(table(mono.d6.strict[which(mono.d6.strict$with.multi.label != "Unknown"), "with.multi.label"]) * 100/nrow(mono.d6.strict))
plot.bar.chart(d6.cells.singles.freq, "~/Desktop/larry_d4_d6_mono_bar_2.pdf", 5, 7, "d6 siblings")

  1. Monocyte-Neutrophil hybrid clones
d6.cells.singles.freq <- as.data.frame(table(mono.neutro.d6.strict[which(mono.neutro.d6.strict$with.multi.label != "Unknown"), "with.multi.label"]) * 100/nrow(mono.neutro.d6.strict))
plot.bar.chart(d6.cells.singles.freq, "~/Desktop/larry_d4_d6_mono_neutro_bar_2.pdf", 5, 7, "d6 siblings")

Overall, we have shown with the lineage data that these hybrid cells are lineage restricted from their day 4 to day 6 siblings.

LS0tCnRpdGxlOiAiV2VpbnJlYiBhbmQgUm9kcmlndWV6LUZyYXRpY2VsbGkgZXQgYWwuLCAyMDIwIEFuYWx5c2lzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyMgQnJpZWYgRGVzY3JpcHRpb24KSW4gdGhpcyBkb2N1bWVudCwgd2UgZG9jdW1lbnQgdGhlIGFuYWx5c2lzIG9mIFdlaW5yZWIgYW5kIFJvZHJpZ3Vlei1GcmF0aWNlbGxpIGV0IGFsLiwgMjAyMCwgbGluZWFnZSB0cmFjaW5nIChMQVJSWSkgaGVtYXRvcG9pZXRpYyBzdGVtIGFuZCBwcm9nZW5pdG9yIGNlbGwgZGlmZmVyZW50aWF0aW9uIGluIHZpdHJvLiBXZSB1c2UgdGhpcyBkYXRhc2V0IHRvIHNob3djYXNlIHRoZSBhcHBsaWNhdGlvbiBvZiBDYXB5YmFyYSwgYW5kIHRoZSBiaW9sb2dpY2FsIHJlbGV2YW5jZSBvZiB0aGUgaHlicmlkIGNlbGxzLiBJbiB0aGlzIG5vdGVib29rLCB3ZSBmaXJzdCBydW4gQ2FweWJhcmEgb24gdGhlIExBUlJZIGRhdGFzZXQgYW5kIGlkZW50aWZ5IGtleSBoeWJyaWQgcG9wdWxhdGlvbnMuCgpGb3IgZGV0YWlscyBvZiB0aGUgZGF0YXNldCwgcGxlYXNlIHJlZmVyIHRvIHRoZSBXZWlucmViIHBhcGVyIGhlcmUgKGh0dHBzOi8vd3d3LnNjaWVuY2Uub3JnL2RvaS8xMC4xMTI2L3NjaWVuY2UuYWF3MzM4MT91cmxfdmVyPVozOS44OC0yMDAzJnJmcl9pZD1vcmk6cmlkOmNyb3NzcmVmLm9yZyZyZnJfZGF0PWNyX3B1YiUyMCUyMDBwdWJtZWQpLiBGb3IgZGV0YWlscyBvZiBDYXB5YmFyYSwgcGxlYXNlIHJlZmVyIHRvIHRoZSBDYXB5YmFyYSBwYXBlciBoZXJlIChodHRwczovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvcGlpL1MxOTM0NTkwOTIyMDAwOTk2P2RnY2lkPWNvYXV0aG9yKS4KCiMjIyBMb2FkIGxpYnJhcmllcwpgYGB7cn0KbGlicmFyeShDYXB5YmFyYSkKbGlicmFyeShnZ3Bsb3QyKQpgYGAKCiMjIyBMb2FkIGNvcmUgZnVuY3Rpb25zIGZvciBwbG90dGluZyBhbmQgY2xvbmFsIGRhdGEgaW50ZXJwcmV0YXRpb24KYGBge3J9CnBsb3QuYmFyLmNoYXJ0IDwtIGZ1bmN0aW9uKGZyZXF1ZW5jeS50YWJsZSwgZmlsZXBhdGgsIHdpZHRoLCBoZWlnaHQsIGxhYmVsLCB0by5maWxlID0gRkFMU0UpIHsKICAKICBmcmVxLnNvcnQgPC0gZnJlcXVlbmN5LnRhYmxlW29yZGVyKGZyZXF1ZW5jeS50YWJsZSRGcmVxLCBkZWNyZWFzaW5nID0gVCksIF0KICBmcmVxLnNvcnQuc3ViIDwtIGZyZXEuc29ydAogIGZyZXEuc29ydC5zdWIkVmFyMSA8LSBhcy5jaGFyYWN0ZXIoZnJlcS5zb3J0LnN1YiRWYXIxKQogIGZyZXEuc29ydC5zdWIkVmFyMSA8LSBmYWN0b3IoZnJlcS5zb3J0LnN1YiRWYXIxLCBsZXZlbHMgPSBmcmVxLnNvcnQuc3ViJFZhcjEsIG9yZGVyZWQgPSBUKQogIAogIAogIHAxIDwtIGdncGxvdChmcmVxLnNvcnQuc3ViLCBhZXMoeCA9IGxhYmVsLCB5ID0gRnJlcSwgZmlsbCA9IFZhcjEpKSArCiAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyAKICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFpcmVkIikgKwogICAgbGFicyh4ID0gIiIsIHkgPSAiUGVyY2VudGFnZSBDb21wb3NpdGlvbiIpICsgCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDEyKSwKICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxMiksCiAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkLml0YWxpYyIsIHNpemUgPSAxMiksCiAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkLml0YWxpYyIsIHNpemUgPSAxMiksCiAgICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgIHRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZC5pdGFsaWMiLCBzaXplID0gMTIpLAogICAgICAgICAgYXhpcy5saW5lLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiksCiAgICAgICAgICBheGlzLmxpbmUueCA9IGVsZW1lbnRfYmxhbmsoKSkKICBpZiAodG8uZmlsZSkgewogICAgcGRmKGZpbGVwYXRoLCB3aWR0aCA9IHdpZHRoLCBoZWlnaHQgPSBoZWlnaHQsIHBhcGVyID0gInNwZWNpYWwiKQogICAgcHJpbnQocDEpCiAgICBkZXYub2ZmKCkKICB9IGVsc2UgewogICAgcHJpbnQocDEpCiAgfQp9CmBgYAoKYGBge3J9CmdldC50b3AudGhyZWUuZmF0ZXMgPC0gZnVuY3Rpb24oaW5kLmNsb25lLmluZm8sIGxhYmVsLmNvbCkgewogIGlmIChucm93KGluZC5jbG9uZS5pbmZvKSA9PSAxKSB7CiAgICAgIHJldHVybihkYXRhLmZyYW1lKFZhcjEgPSBpbmQuY2xvbmUuaW5mb1sxLGxhYmVsLmNvbF0sIEZyZXEgPSAxMDAsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKSkKICB9CiAgZnJlcS50YWJsZSA8LSBhcy5kYXRhLmZyYW1lKHNvcnQodGFibGUoaW5kLmNsb25lLmluZm9bLGxhYmVsLmNvbF0pLCBkZWNyZWFzaW5nID0gVCkgKiAxMDAvbnJvdyhpbmQuY2xvbmUuaW5mbykpCiAgaWYgKG5yb3coZnJlcS50YWJsZSkgPD0gMykgewogICAgaWYgKG5yb3coZnJlcS50YWJsZSkgPT0gMSkgewogICAgICByZXR1cm4oZGF0YS5mcmFtZShWYXIxID0gcm93bmFtZXMoZnJlcS50YWJsZSksIEZyZXEgPSAxMDAsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKSkKICAgIH0KICAgIHJldHVybihmcmVxLnRhYmxlKQogIH0gZWxzZSB7CiAgICByZXR1cm4oZnJlcS50YWJsZVtjKDE6MyksXSkKICB9Cn0KCmNvbXB1dGUudG9wLnRocmVlLmZhdGVzLmZvci5hbGwgPC0gZnVuY3Rpb24oY29tcG9zaXRlLmNsb25hbCwgbGFiZWwuY29sKSB7CiAgdW5pcXVlLmNsb25lcy5pbi5jb21wIDwtIHVuaXF1ZShjb21wb3NpdGUuY2xvbmFsJGopCiAgdG9wLnRocmVlLmZhdGVzLmRmIDwtIGRhdGEuZnJhbWUoKQogIGZvciAodWMuaW4uY29tcCBpbiB1bmlxdWUuY2xvbmVzLmluLmNvbXApIHsKICAgIGN1cnIuY2xvbmUgPC0gY29tcG9zaXRlLmNsb25hbFt3aGljaChjb21wb3NpdGUuY2xvbmFsJGogPT0gdWMuaW4uY29tcCksXQogICAgdG9wLmZhdGVzIDwtIGdldC50b3AudGhyZWUuZmF0ZXMoY3Vyci5jbG9uZSwgbGFiZWwuY29sKQogICAgCiAgICBjdXJyLnRvcCA8LSBjdXJyLmNsb25lW3doaWNoKGN1cnIuY2xvbmVbLGxhYmVsLmNvbF0gJWluJSB0b3AuZmF0ZXMkVmFyMSksIF0KICAgIGlmIChucm93KHRvcC50aHJlZS5mYXRlcy5kZikgPD0gMCkgewogICAgICB0b3AudGhyZWUuZmF0ZXMuZGYgPC0gY3Vyci50b3AKICAgIH0gZWxzZSB7CiAgICAgIHRvcC50aHJlZS5mYXRlcy5kZiA8LSByYmluZCh0b3AudGhyZWUuZmF0ZXMuZGYsIGN1cnIudG9wKQogICAgfQogIH0KICAKICByZXR1cm4odG9wLnRocmVlLmZhdGVzLmRmKQp9CmBgYAoKYGBge3J9CmNvbXB1dGUuY29tcG9zaXRpb24uY2xvbmVzIDwtIGZ1bmN0aW9uKGNsb25hbC5kYXRhLCBsYWJlbC5jb2wpIHsKICBjbG9uZXMudW5pcSA8LSB1bmlxdWUoY2xvbmFsLmRhdGEkaikKICBjb21wb3NpdGlvbi5jbG9uZXMgPC0gZGF0YS5mcmFtZSgpCiAgZm9yICh1YyBpbiBjbG9uZXMudW5pcSkgewogICAgY3Vyci5jbG9uZS5zdWIgPC0gY2xvbmFsLmRhdGFbd2hpY2goY2xvbmFsLmRhdGEkaiA9PSB1YyksIF0KICAgIGN1cnIuY2xvbmUuc3ViJG5ldy5jZWxsLnR5cGUgPC0gcGFzdGUwKCJEYXlfIiwgY3Vyci5jbG9uZS5zdWIkdHAsICJfIiwgY3Vyci5jbG9uZS5zdWJbLGxhYmVsLmNvbF0pCiAgICBjdXJyLmNvbXAgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShjdXJyLmNsb25lLnN1YiRuZXcuY2VsbC50eXBlKSAqIDEwMC9ucm93KGN1cnIuY2xvbmUuc3ViKSkKICAgIGN1cnIuY29tcCRjbG9uZSA8LSB1YwogICAgCiAgICBpZiAobnJvdyhjb21wb3NpdGlvbi5jbG9uZXMpIDw9IDApewogICAgICBjb21wb3NpdGlvbi5jbG9uZXMgPC0gY3Vyci5jb21wCiAgICB9IGVsc2UgewogICAgICBjb21wb3NpdGlvbi5jbG9uZXMgPC0gcmJpbmQoY29tcG9zaXRpb24uY2xvbmVzLCBjdXJyLmNvbXApCiAgICB9CiAgfQogIAogIHJldHVybihjb21wb3NpdGlvbi5jbG9uZXMpCn0KCmNvbXB1dGUuY29tcG9zaXRpb24uY2xvbmVzLmQyLjYgPC0gZnVuY3Rpb24oY2xvbmFsLmRhdGEsIGxhYmVsLmNvbCkgewogIGNsb25lcy51bmlxIDwtIHVuaXF1ZShjbG9uYWwuZGF0YSRqKQogIGNvbXBvc2l0aW9uLmNsb25lcyA8LSBkYXRhLmZyYW1lKCkKICBmb3IgKHVjIGluIGNsb25lcy51bmlxKSB7CiAgICBjdXJyLmNsb25lLnN1YiA8LSBjbG9uYWwuZGF0YVt3aGljaChjbG9uYWwuZGF0YSRqID09IHVjKSwgXQogICAgY3Vyci5jbG9uZS5zdWIkbmV3LmNlbGwudHlwZSA8LSBwYXN0ZTAoIkRheV8iLCBjdXJyLmNsb25lLnN1YiR0cCwgIl8iLCBjdXJyLmNsb25lLnN1YlssbGFiZWwuY29sXSkKICAgIAogICAgI2QyLmNvbXAgPC0gZ2V0LnRvcC50aHJlZS5mYXRlcyhjdXJyLmNsb25lLnN1Ylt3aGljaChjdXJyLmNsb25lLnN1YiR0cCA9PSAyKSwgXSwgIndpdGgubXVsdGkubGFiZWwiKQogICAgZDQuY29tcCA8LSBnZXQudG9wLnRocmVlLmZhdGVzKGN1cnIuY2xvbmUuc3ViW3doaWNoKGN1cnIuY2xvbmUuc3ViJHRwID09IDQpLCBdLCAid2l0aC5tdWx0aS5sYWJlbCIpCiAgICBkNi5jb21wIDwtIGdldC50b3AudGhyZWUuZmF0ZXMoY3Vyci5jbG9uZS5zdWJbd2hpY2goY3Vyci5jbG9uZS5zdWIkdHAgPT0gNiksIF0sICJ3aXRoLm11bHRpLmxhYmVsIikgICAgCiAgICAjIGlmIChucm93KGQyLmNvbXApID49IDEpIGQyLmNvbXAkZGF5IDwtICJEYXkgMiIKICAgIGlmIChucm93KGQ0LmNvbXApID49IDEpIGQ0LmNvbXAkZGF5IDwtICJEYXkgNCIKICAgIGlmIChucm93KGQ2LmNvbXApID49IDEpIGQ2LmNvbXAkZGF5IDwtICJEYXkgNiIKICAgIGN1cnIuY29tcCA8LSByYmluZChkNi5jb21wLCBkNC5jb21wKQogICAgY3Vyci5jb21wJGNsb25lIDwtIHVjCiAgICAKICAgIGQ0LnN1YnNldCA8LSBjdXJyLmNsb25lLnN1Ylt3aGljaChjdXJyLmNsb25lLnN1YiR0cCA9PSA0KSxdCiAgICB0aHJlZS5mYXRlLmZpbHRlciA8LSBnZXQudG9wLnRocmVlLmZhdGVzKGQ0LnN1YnNldCwgIndpdGgubXVsdGkubGFiZWwiKQogICAgCiAgICBpZiAobnJvdyh0aHJlZS5mYXRlLmZpbHRlcikgPT0gMykgewogICAgICBjb21wLmZ1bGwgPC0gcmJpbmQocmJpbmQoY3Vyci5jb21wLCBjdXJyLmNvbXApLCBjdXJyLmNvbXApCiAgICB9IGVsc2UgaWYgKG5yb3codGhyZWUuZmF0ZS5maWx0ZXIpID09IDIpIHsKICAgICAgY29tcC5mdWxsIDwtIHJiaW5kKGN1cnIuY29tcCwgY3Vyci5jb21wKQogICAgfSBlbHNlIHsKICAgICAgY29tcC5mdWxsIDwtIGN1cnIuY29tcAogICAgfQogICAgCiAgICBjb21wLmZ1bGwkZG9taW5hdGUuZDQuZmF0ZSA8LSByZXAodGhyZWUuZmF0ZS5maWx0ZXIkVmFyMSwgZWFjaCA9IG5yb3coY3Vyci5jb21wKSkKICAgIAogICAgaWYgKG5yb3coY29tcG9zaXRpb24uY2xvbmVzKSA8PSAwKXsKICAgICAgY29tcG9zaXRpb24uY2xvbmVzIDwtIGNvbXAuZnVsbAogICAgfSBlbHNlIHsKICAgICAgY29tcG9zaXRpb24uY2xvbmVzIDwtIHJiaW5kKGNvbXBvc2l0aW9uLmNsb25lcywgY29tcC5mdWxsKQogICAgfQogIH0KICAKICByZXR1cm4oY29tcG9zaXRpb24uY2xvbmVzKQp9CgpgYGAKCmBgYHtyfQppZGVudGlmeS5zdHJpY3QuY2xvbmVzIDwtIGZ1bmN0aW9uKGNsb25hbC5kYXRhLCBzdHJpY3QubGFiZWwpIHsKICB1bmlxdWUuY2xvbmVzIDwtIHVuaXF1ZShjbG9uYWwuZGF0YSRqKQogIHN0cmljdC5kNC5jbG9uZSA8LSBkYXRhLmZyYW1lKCkKICBmb3IgKHVjIGluIHVuaXF1ZS5jbG9uZXMpIHsKICAgIGN1cnIuY2xvbmUgPC0gY2xvbmFsLmRhdGFbd2hpY2goY2xvbmFsLmRhdGEkaiA9PSB1YyksIF0KICAgIGN1cnIuY2xvbmUuZDQgPC0gY3Vyci5jbG9uZVt3aGljaChjdXJyLmNsb25lJHRwID09IDQpLF0KICAgIGlmIChucm93KGN1cnIuY2xvbmUuZDQpID4gMCkgewogICAgICBjZWxsLnR5cGUuY2xvbmUuZDQgPC0gYXMuZGF0YS5mcmFtZShzb3J0KHRhYmxlKGN1cnIuY2xvbmUuZDQkd2l0aC5tdWx0aS5sYWJlbCksIGRlY3JlYXNpbmcgPSBUKSkKICAgICAgaWYgKG5yb3coY2VsbC50eXBlLmNsb25lLmQ0KSA9PSAxKSB7CiAgICAgICAgaWYgKHJvd25hbWVzKGNlbGwudHlwZS5jbG9uZS5kNCkgPT0gc3RyaWN0LmxhYmVsKSB7CiAgICAgICAgICBzdHJpY3QuZDQuY2xvbmUgPC0gYyhzdHJpY3QuZDQuY2xvbmUsIHVjKQogICAgICAgIH0KICAgICAgfSAKICAgICAgIyBlbHNlIHsKICAgICAgIyAgIGlmICgoY2VsbC50eXBlLmNsb25lLmQ0JEZyZXFbMV0gPiAgY2VsbC50eXBlLmNsb25lLmQ0JEZyZXFbMl0pICYgY2VsbC50eXBlLmNsb25lLmQ0JFZhcjFbMV0gPT0gc3RyaWN0LmxhYmVsKSB7CiAgICAgICMgICAgIHN0cmljdC5kNC5jbG9uZSA8LSBjKHN0cmljdC5kNC5jbG9uZSwgdWMpCiAgICAgICMgICB9CiAgICAgICMgfQogICAgfQogIH0KICByZXR1cm4oc3RyaWN0LmQ0LmNsb25lKQp9CmBgYAoKCiMjIyBDYXB5YmFyYQpJbiBnZW5lcmFsLCBmb3IgcnVudGltZSBjb25zaWRlcmF0aW9uLCB3ZSBwcm9jZXNzZWQgdGhlIGRhdGFzZXQgb24gYSBIaWdoIFBlcmZvcm1hbmNlIENvbXB1dGluZyByZXNvdXJjZS4gSGVyZSwgd2UgaW5jbHVkZSB0aGUgaW50ZXJtZWRpYXRlIGZpbGVzLCBzdWNoIGFzIHRoZSByZWZlcmVuY2UsIFFQIG91dGNvbWVzLCBhbmQgcGVybXV0YXRpb24gcmVzdWx0cywgaW4gdGhpcyBmb2xkZXIgZm9yIGZhc3RlciBwcm9jZXNzaW5nLgoKIyMjIyBDb25zdHJ1Y3QgdGhlIEhpZ2gtUmVzb2x1dGlvbiBSZWZlcmVuY2UKSGVyZSB3ZSBjb25zdHJ1Y3QgdGhlIHJlZmVyZW5jZSBiYXNlZCBvbiBEYXkgNiBtYWpvciBoZW1hdG9wb2lldGljIHBvcHVsYXRpb25zLCBpbmNsdWRpbmcgYmFzb3BoaWxzLCBlb3Npbm9waGlscywgbW9ub2N5dGVzLCBuZXV0cm9waGlscywgYW5kIG1hc3QgY2VsbHMuIFRoaXMgd2FzIGV4ZWN1dGVkIG9uIHRoZSBjb21wdXRpbmcgY2x1c3RlciB3aXRoIDYgY29yZXMgYW5kIDEyOEcgbWVtb3J5LiBDb25zaWRlcmluZyB0aGUgbG9uZyBydW50aW1lLCB3ZSB3aWxsIGRpcmVjdGx5IGltcG9ydCB0aGUgcmVmZXJlbmNlIG91dHB1dCBmcm9tIHRoaXMgc2NyaXB0LgoKMSkgTG9hZCB0aGUgbWV0YSBkYXRhCmBgYHtyfQptZXRhLmxhcnJ5IDwtIHJlYWQudGFibGUoIn4vRGVza3RvcC9SZXByb2R1Y2liaWxpdHkvRmlndXJlIDMvSW50ZXJtZWRpYXRlcy9MQVJSWSBTdGF0ZSBGYXRlIERhdGEvc3RhdGVGYXRlX2luVml0cm9fbWV0YWRhdGEudHh0Iiwgc2VwID0gIlx0IiwgaGVhZGVyID0gVCwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnJvd25hbWVzKG1ldGEubGFycnkpIDwtIHBhc3RlMCgiQ2VsbF8iLCBzZXEoMSwxMzA4ODcpKQpsc2sgPC0gbWV0YS5sYXJyeVt3aGljaChtZXRhLmxhcnJ5JFN0YXJ0aW5nLnBvcHVsYXRpb24gPT0gIkxpbi1LaXQrU2NhMSsiKSwgXQpgYGAKCjIpIFNlbGVjdGlvbiBvZiBwcm9wZXIgY2VsbCB0eXBlcyBhbmQgY29uc3RydWN0IHJlZmVyZW5jZXMKYGBge3IsIGV2YWw9RkFMU0V9CiMjIyBPbmx5IHRha2UgRGF5IDQgYW5kIERheSA2IGNlbGxzIGZvciByZWZlcmVuY2UKbHNrLmRheS40LjYgPC0gbHNrW3doaWNoKGxzayRUaW1lLnBvaW50ICVpbiUgYyg0LDYpKSwgXQpsc2suZGF5LjQuNiA8LSBsc2suZGF5LjQuNlt3aGljaChsc2suZGF5LjQuNiRDZWxsLnR5cGUuYW5ub3RhdGlvbiAhPSAiVW5kaWZmZXJlbnRpYXRlZCIpLCBdCgpsc2suZGF5LjYgPC0gbHNrW3doaWNoKGxzayRUaW1lLnBvaW50ICVpbiUgYyg2KSksIF0KbHNrLmRheS42IDwtIGxzay5kYXkuNlt3aGljaChsc2suZGF5LjYkQ2VsbC50eXBlLmFubm90YXRpb24gIT0gIlVuZGlmZmVyZW50aWF0ZWQiKSwgXQoKbHNrLmluLnZpdHJvIDwtIHJlYWRSRFMoIn4vRGVza3RvcC9SZXByb2R1Y2liaWxpdHkvRmlndXJlIDMvSW50ZXJtZWRpYXRlcy9MQVJSWSBTdGF0ZSBGYXRlIERhdGEvbHNrX21hdHJpeC5SZHMiKQpjdC5mcmVxLmQ2IDwtIGFzLmRhdGEuZnJhbWUodGFibGUobHNrLmRheS42JENlbGwudHlwZS5hbm5vdGF0aW9uKSkKCiMjIHJlbW92ZSB0aGUgY2VsbCB0eXBlIHdpdGggbGVzcyB0aGFuIDMwIGNlbGxzCmxlc3MudGhhbi4zMC5jdC5kNiA8LSBhcy5jaGFyYWN0ZXIoY3QuZnJlcS5kNlt3aGljaChjdC5mcmVxLmQ2JEZyZXEgPD0gMzApLCAiVmFyMSJdKQoKbHNrLmRheS42LnN1YiA8LSBsc2suZGF5LjZbLXdoaWNoKGxzay5kYXkuNiRDZWxsLnR5cGUuYW5ub3RhdGlvbiAlaW4lIGxlc3MudGhhbi4zMC5jdC5kNiksIF0KbHNrLmluLnZpdHJvIDwtIGFzLm1hdHJpeCh0KGxzay5pbi52aXRyb1tyb3duYW1lcyhsc2suZGF5LjQuNiksXSkpCgpsc2suZGF5LjYuc3ViLndvLm1lZyA8LSBsc2suZGF5LjYuc3ViW3doaWNoKGxzay5kYXkuNi5zdWIkQ2VsbC50eXBlLmFubm90YXRpb24gIT0gIk1lZyIpLCBdCmxzay5kYXkuNi5zdWIud28ubWVnLmx5bSA8LSBsc2suZGF5LjYuc3ViLndvLm1lZ1t3aGljaChsc2suZGF5LjYuc3ViLndvLm1lZyRDZWxsLnR5cGUuYW5ub3RhdGlvbiAhPSAiTHltcGhvaWQiKSwgXQoKcmVmZXJlbmNlLm5ldy5kNi5uby5tZWcubHltIDwtIGNvbnN0cnVjdC5oaWdoLnJlcy5yZWZlcmVuY2UobHNrLmluLnZpdHJvWyxyb3duYW1lcyhsc2suZGF5LjYuc3ViLndvLm1lZy5seW0pXSwgbHNrLmRheS42LnN1Yi53by5tZWcubHltLCBjcml0ZXJpYSA9ICJDZWxsLnR5cGUuYW5ub3RhdGlvbiIpCgpzYXZlUkRTKHJlZmVyZW5jZS5uZXcuZDYubm8ubWVnLmx5bSwgIjAzX25ld19sc2tfcmVmX3dpdGhfY2VsbHNfb3Zlcl8zMF93b191bmRpZmZfZDZfbm9fbWVnX2x5bS5SZHMiKQpgYGAKCiMjIyMgUXVhZHJhdGljIFByb2dyYW1taW5nClRoaXMgd2FzIGV4ZWN1dGVkIG9uIHRoZSBjb21wdXRpbmcgY2x1c3RlciB3aXRoIDQgY29yZXMgYW5kIDIwMEcgbWVtb3J5LiBDb25zaWRlcmluZyB0aGUgbG9uZyBydW50aW1lLCB3ZSB3aWxsIGRpcmVjdGx5IGltcG9ydCB0aGUgUVAgb3V0cHV0IGZyb20gdGhpcyBzY3JpcHQuCgoxKSBSZWFkIGluIHRoZSByZWZlcmVuY2UgZGF0YSBhbmQgbG9hZCBpbnRvIHByb3BlciB2YXJpYWJsZXMKYGBge3J9CnJlZi5yc2x0LmxzayA8LSByZWFkUkRTKCJ+L0Rlc2t0b3AvTW9ycmlzIExhYi9NYW51c2NyaXB0cy9DYXB5YmFyYS9SZXZpc2lvbi9MQVJSWSBSZXJ1biAxMDIzMjEvMDNfbmV3X2xza19yZWZfd2l0aF9jZWxsc19vdmVyXzMwX3dvX3VuZGlmZl9kNl9ub19tZWdfbHltLlJkcyIpCnJlZi5kZi5sc2sgPC0gcmVmLnJzbHQubHNrW1szXV0KcmVmLnNjLmxzayA8LSByZWYucnNsdC5sc2tbWzFdXQpyZWYubWV0YS5sc2sgPC0gcmVmLnJzbHQubHNrW1syXV0KYGBgCgoyKSBQcmVwYXJlIHRoZSBkYXRhIG1hdHJpY2VzIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzCmBgYHtyLCBldmFsPUZBTFNFfQpsc2suaW4udml0cm8udW5kaWZmZXJlbnRpYXRlZCA8LSBsc2suaW4udml0cm9bcm93bmFtZXMobHNrKVt3aGljaChsc2skQ2VsbC50eXBlLmFubm90YXRpb24gPT0gIlVuZGlmZmVyZW50aWF0ZWQiKV0sIF0KbHNrLmluLnZpdHJvLnVuZGlmZmVyZW50aWF0ZWQgPC0gYXMubWF0cml4KHQobHNrLmluLnZpdHJvLnVuZGlmZmVyZW50aWF0ZWQpKQoKbHNrLmluLnZpdHJvLmRpZmZlcmVudGlhdGVkIDwtIGxzay5pbi52aXRyb1tyb3duYW1lcyhsc2spW3doaWNoKGxzayRDZWxsLnR5cGUuYW5ub3RhdGlvbiAhPSAiVW5kaWZmZXJlbnRpYXRlZCIpXSwgXQoKbHNrLmluLnZpdHJvLmRpZmZlcmVudGlhdGVkLnN1YiA8LSBsc2suaW4udml0cm8uZGlmZmVyZW50aWF0ZWRbc2V0ZGlmZihyb3duYW1lcyhsc2suaW4udml0cm8uZGlmZmVyZW50aWF0ZWQpLCByZWYubWV0YS5sc2skY2VsbC5iYyksIF0KbHNrLmluLnZpdHJvLmRpZmZlcmVudGlhdGVkLnN1YiA8LSBhcy5tYXRyaXgodChsc2suaW4udml0cm8uZGlmZmVyZW50aWF0ZWQuc3ViKSkKYGBgCgozKSBSdW4gUXVhZHJhdGljIFByb2dyYW1taW5nCmBgYHtyLCBldmFsPUZBTFNFfQojIE1lYXN1cmUgY2VsbCBpZGVudGl0eSBpbiB0aGUgcmVmZXJlbmNlIGRhdGFzZXQgYXMgYSBiYWNrZ3JvdW5kIApzaW5nbGUucm91bmQuUVAuYW5hbHlzaXMocmVmLmRmLmxzaywgcmVmLnNjLmxzaywgbi5jb3JlcyA9IDQsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHNhdmUudG8ucGF0aCA9ICIvc2NyYXRjaC9zbWxhYi9rb25ndy9sYXJyeV9yZXJ1bi8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBzYXZlLnRvLmZpbGVuYW1lID0gIjA1X0xTS19CYXNlZF9MU0tfRGlmZmVyZW50aWF0ZWRfU2FtcGxlX1JlZl9ub19tZWdfbHltX25vX2ZvcmNlIiwgdW5peC5wYXIgPSBUUlVFKQoKIyBNZWFzdXJlIGNlbGwgaWRlbnRpdHkgaW4gdGhlIHF1ZXJ5IGRhdGFzZXQgCnNpbmdsZS5yb3VuZC5RUC5hbmFseXNpcyhyZWYuZGYubHNrLCBsc2suaW4udml0cm8uZGlmZmVyZW50aWF0ZWQuc3ViLCBuLmNvcmVzID0gNCwgCiAgICAgICAgICAgICAgICAgICAgICAgICBzYXZlLnRvLnBhdGggPSAiL3NjcmF0Y2gvc21sYWIva29uZ3cvbGFycnlfcmVydW4vIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICBzYXZlLnRvLmZpbGVuYW1lID0gIjA1X0xTS19CYXNlZF9MU0tfRGlmZmVyZW50aWF0ZWRfU2FtcGxlX1Rlc3Rfbm9fbWVnX2x5bV9ub19mb3JjZSIsIHVuaXgucGFyID0gVFJVRSkKCiMgTWVhc3VyZSBjZWxsIGlkZW50aXR5IGluIHRoZSBxdWVyeSBkYXRhc2V0IApzaW5nbGUucm91bmQuUVAuYW5hbHlzaXMocmVmLmRmLmxzaywgbHNrLmluLnZpdHJvLnVuZGlmZmVyZW50aWF0ZWQsIG4uY29yZXMgPSA0LCAKICAgICAgICAgICAgICAgICAgICAgICAgIHNhdmUudG8ucGF0aCA9ICIvc2NyYXRjaC9zbWxhYi9rb25ndy9sYXJyeV9yZXJ1bi8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHNhdmUudG8uZmlsZW5hbWUgPSAiMDVfTFNLX0Jhc2VkX0xTS19VbmRpZmZlcmVudGlhdGVkX1NhbXBsZV9UZXN0X25vX21lZ19seW1fbm9fZm9yY2UiLCB1bml4LnBhciA9IFRSVUUpCgpgYGAKCjQpIEhlcmUgd2UgbG9hZCB0aGUgcHJlLWNhbGN1bGF0ZWQgUVAgcmVzdWx0cyBmb3IgdGhpcyBhbmFseXNpcy4KYGBge3J9CiMjIEJhY2tncm91bmQgUVAgc2NvcmVzCnJlZi5xcCA8LSByZWFkLmNzdigifi9EZXNrdG9wL1JlcHJvZHVjaWJpbGl0eS9GaWd1cmUgMy9JbnRlcm1lZGlhdGVzL1FQX091dGNvbWVzLzA1X0xTS19CYXNlZF9MU0tfRGlmZmVyZW50aWF0ZWRfU2FtcGxlX1JlZl9ub19tZWdfbHltX25vX2ZvcmNlX3NjYWxlLmNzdiIsIGhlYWRlciA9IFQsIHJvdy5uYW1lcyA9IDEsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQoKIyMgUVAgc2NvcmVzIGZvciB0aGUgdW5kaWZmZXJlbnRpYXRlZCBjZWxscwp1bmRpZmZlcmVudGlhdGVkLnFwIDwtIHJlYWQuY3N2KCJ+L0Rlc2t0b3AvUmVwcm9kdWNpYmlsaXR5L0ZpZ3VyZSAzL0ludGVybWVkaWF0ZXMvUVBfT3V0Y29tZXMvMDVfTFNLX0Jhc2VkX0xTS19VbmRpZmZlcmVudGlhdGVkX1NhbXBsZV9UZXN0X25vX21lZ19seW1fbm9fZm9yY2Vfc2NhbGUuY3N2IiwgaGVhZGVyID0gVCwgcm93Lm5hbWVzID0gMSwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgojIyBRUCBzY29yZXMgZm9yIHRoZSBkaWZmZXJlbnRpYXRlZCBjZWxscwpkaWZmZXJlbnRpYXRlZC5xcCA8LSByZWFkLmNzdigifi9EZXNrdG9wL1JlcHJvZHVjaWJpbGl0eS9GaWd1cmUgMy9JbnRlcm1lZGlhdGVzL1FQX091dGNvbWVzLzA1X0xTS19CYXNlZF9MU0tfRGlmZmVyZW50aWF0ZWRfU2FtcGxlX1Rlc3Rfbm9fbWVnX2x5bV9ub19mb3JjZV9zY2FsZS5jc3YiLCBoZWFkZXIgPSBULCByb3cubmFtZXMgPSAxLCBzdHJpbmdzQXNGYWN0b3JzID0gRikKYGBgCgojIyMjIEVtcGlyaWNhbCBwLXZhbHVlIGNhbGN1bGF0aW9uClRvIGNhbGN1bGF0ZSB0aGUgZW1waXJpY2FsIHAtdmFsdWUsIHJ1biB0aGUgZm9sbG93aW5nIGxpbmVzLiBIZXJlIHdlIHNraXAgdGhlc2UgbGluZXMgdG8gbG9hZCBwcmV2aW91c2x5IG9idGFpbmVkIHJlc3VsdHMuCmBgYHtyLCBldmFsPUZBTFNFfQpjb2wuc3ViIDwtIG5jb2wocmVmLnFwKSAtIDIKCiMgQ29uZHVjdCByZWZlcmVuY2UgcmFuZG9taXphdGlvbiB0byBnZXQgZW1waXJpY2FsIHAtdmFsdWUgbWF0cml4CnJlZi5wZXJjLmxpc3QgPC0gcGVyY2VudGFnZS5jYWxjKGFzLm1hdHJpeChyZWYucXBbLGMoMTpjb2wuc3ViKV0pLCBhcy5tYXRyaXgocmVmLnFwWyxjKDE6Y29sLnN1YildKSkKCiMgQ29uZHVjdCB0ZXN0IHJhbmRvbWl6YXRpb24gdG8gZ2V0IGVtcGlyaWNhbCBwLXZhbHVlIG1hdHJpeApkaWZmLnBlcmMubGlzdCA8LSBwZXJjZW50YWdlLmNhbGMoYXMubWF0cml4KGRpZmZlcmVudGlhdGVkLnFwWyxjKDE6Y29sLnN1YildKSwgYXMubWF0cml4KHJlZi5xcFssYygxOmNvbC5zdWIpXSkpCnVuZGlmZi5wZXJjLmxpc3QgPC0gcGVyY2VudGFnZS5jYWxjKGFzLm1hdHJpeCh1bmRpZmZlcmVudGlhdGVkLnFwWyxjKDE6Y29sLnN1YildKSwgYXMubWF0cml4KHJlZi5xcFssYygxOmNvbC5zdWIpXSkpCmBgYAoKSGVyZSB3ZSBsb2FkIHRoZSBwcmV2aW91cyBlbXBpcmljYWwgcC12YWx1ZSBkYXRhLgpgYGB7cn0KcmVmLnBlcm0ubHMgPC0gcmVhZFJEUygifi9EZXNrdG9wL01vcnJpcyBMYWIvTWFudXNjcmlwdHMvQ2FweWJhcmEvUmV2aXNpb24vTEFSUlkgUmVydW4gMTAyMzIxLzA1X3JlZl9wZXJjZW50X2xpc3RfbmV3X3JlZl9kNl9kaWZmZXJlbnRpYXRlZF9ub19mb3JjZS5SZHMiKQpwZXJtLnVuZGlmZi5hbGwgPC0gcmVhZFJEUygifi9EZXNrdG9wL01vcnJpcyBMYWIvTWFudXNjcmlwdHMvQ2FweWJhcmEvUmV2aXNpb24vTEFSUlkgUmVydW4gMTAyMzIxLzA1X3VuZGlmZl9wZXJjZW50X2xpc3RfbmV3X3JlZl9kNl9kaWZmZXJlbnRpYXRlZF9ub19mb3JjZS5SZHMiKQpwZXJtLmRpZmYuYWxsIDwtIHJlYWRSRFMoIn4vRGVza3RvcC9Nb3JyaXMgTGFiL01hbnVzY3JpcHRzL0NhcHliYXJhL1JldmlzaW9uL0xBUlJZIFJlcnVuIDEwMjMyMS8wNV9kaWZmX3BlcmNlbnRfbGlzdF9uZXdfcmVmX2Q2X2RpZmZlcmVudGlhdGVkX25vX2ZvcmNlLlJkcyIpCmBgYAoKIyMjIyBJbml0aWFsIENsYXNzaWZpY2F0aW9uCkhlcmUgd2UgcGVyZm9ybSBpbml0aWFsIGNsYXNzaWZpY2F0aW9uIGJhc2VkIG9uIHF1YWRyYXRpYyBwcm9ncmFtbWluZyBtZXRyaWNzOiBkZXZpYW5jZSwgZXJyb3IsIGFuZCBsYWdyYW5naWFuIG11bHRpcGxpZXJzLiBXZSBtYWlubHkgbGV2ZXJhZ2UgZGV2aWFuY2UgaGVyZSB0byBkaXN0aW5ndWlzaCB1bmtub3duIGNlbGxzIGZyb20gZGlzY3JldGUgY2VsbHMgYW5kIGh5YnJpZCBjZWxscyAobm90ZSwgd2UgdXNlIHRoZSB0ZXJtICdtdWx0aS1pZCcgdG8gcmVmZXIgdG8gaHlicmlkIGNlbGxzKS4KCjEpIENvbnN0cnVjdCB0aGUgaWRlYWwgZGV2aWFuY2UgZGlzdHJpYnV0aW9uIGJhc2VkIG9uIHRoZSBiYWNrZ3JvdW5kIG1hdHJpeApgYGB7cn0KaWRlYWwuZGV2aWFuY2UgPC0gcmVmLnFwWyxjKDE6NSldIC0gMS81CmlkZWFsLmRldmlhbmNlLmFsbCA8LSByb3dTdW1zKGFicyhpZGVhbC5kZXZpYW5jZSkpCmlkZWFsLmRldmlhbmNlLmFsbC5tZWFuIDwtIG1lYW4oaWRlYWwuZGV2aWFuY2UuYWxsKQppZGVhbC5kZXZpYW5jZS5zZCA8LSBzZChpZGVhbC5kZXZpYW5jZS5hbGwpCgpmaXQgPC0gZml0ZGlzdHIoaWRlYWwuZGV2aWFuY2UuYWxsLCAibm9ybWFsIikKCmd1ZXNzZWQubXVsdGkuaWQuZGV2aWFuY2UubWVhbiA8LSBpZGVhbC5kZXZpYW5jZS5hbGwubWVhbiAtIGZpdCRlc3RpbWF0ZVsic2QiXQpndWVzc2VkLnVua25vd24uZGV2aWFuY2UubWVhbiA8LSBndWVzc2VkLm11bHRpLmlkLmRldmlhbmNlLm1lYW4gLSBmaXQkZXN0aW1hdGVbInNkIl0KCnVuZGlmZmVyZW50aWF0ZWQucXAuZGV2aWFuY2UgPC0gYWJzKHVuZGlmZmVyZW50aWF0ZWQucXBbLGMoMTo1KV0gLSAxLzUpCmRpZmZlcmVudGlhdGVkLnFwLmRldmlhbmNlIDwtIGFicyhkaWZmZXJlbnRpYXRlZC5xcFssYygxOjUpXSAtIDEvNSkKCnVuZGlmZmVyZW50aWF0ZWQucXAuZGV2aWFuY2UkdG90YWwuZGV2aWFuY2UgPC0gcm93U3Vtcyh1bmRpZmZlcmVudGlhdGVkLnFwLmRldmlhbmNlKQpkaWZmZXJlbnRpYXRlZC5xcC5kZXZpYW5jZSR0b3RhbC5kZXZpYW5jZSA8LSByb3dTdW1zKGRpZmZlcmVudGlhdGVkLnFwLmRldmlhbmNlKQoKdW5kaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZSA8LSB1bmRpZmZlcmVudGlhdGVkLnFwLmRldmlhbmNlW3Jvd25hbWVzKHVuZGlmZmVyZW50aWF0ZWQucXApLCAidG90YWwuZGV2aWFuY2UiXQpkaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZSA8LSBkaWZmZXJlbnRpYXRlZC5xcC5kZXZpYW5jZVtyb3duYW1lcyhkaWZmZXJlbnRpYXRlZC5xcCksICJ0b3RhbC5kZXZpYW5jZSJdCgojIyBDYWxjdWxhdGUgcC12YWx1ZXMgKFVuZGlmZmVyZW50aWF0ZWQgQ2VsbHMpCnVuZGlmZmVyZW50aWF0ZWQucXAkZGV2aWFuY2UucCA8LSBwbm9ybSh1bmRpZmZlcmVudGlhdGVkLnFwJGRldmlhbmNlLCBtZWFuID0gaWRlYWwuZGV2aWFuY2UuYWxsLm1lYW4sIHNkID0gaWRlYWwuZGV2aWFuY2Uuc2QsIGxvd2VyLnRhaWwgPSBUKQp1bmRpZmZlcmVudGlhdGVkLnFwJGRldmlhbmNlLnAubXVsdGkgPC0gcG5vcm0odW5kaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZSwgbWVhbiA9IGd1ZXNzZWQubXVsdGkuaWQuZGV2aWFuY2UubWVhbiwgc2QgPSBpZGVhbC5kZXZpYW5jZS5zZC8yLCBsb3dlci50YWlsID0gVCkKdW5kaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZS5wLnVua25vd24gPC0gcG5vcm0odW5kaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZSwgbWVhbiA9IGd1ZXNzZWQudW5rbm93bi5kZXZpYW5jZS5tZWFuLCBzZCA9IGlkZWFsLmRldmlhbmNlLnNkLCBsb3dlci50YWlsID0gVCkKCiMjIENhbGN1bGF0ZSBwLXZhbHVlcyAoRGlmZmVyZXRpYXRlZCBDZWxscykKZGlmZmVyZW50aWF0ZWQucXAkZGV2aWFuY2UucCA8LSBwbm9ybShkaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZSwgbWVhbiA9IGlkZWFsLmRldmlhbmNlLmFsbC5tZWFuLCBzZCA9IGlkZWFsLmRldmlhbmNlLnNkLCBsb3dlci50YWlsID0gVCkKZGlmZmVyZW50aWF0ZWQucXAkZGV2aWFuY2UucC5tdWx0aSA8LSBwbm9ybShkaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZSwgbWVhbiA9IGd1ZXNzZWQubXVsdGkuaWQuZGV2aWFuY2UubWVhbiwgc2QgPSBpZGVhbC5kZXZpYW5jZS5zZC8yLCBsb3dlci50YWlsID0gVCkKZGlmZmVyZW50aWF0ZWQucXAkZGV2aWFuY2UucC51bmtub3duIDwtIHBub3JtKGRpZmZlcmVudGlhdGVkLnFwJGRldmlhbmNlLCBtZWFuID0gZ3Vlc3NlZC51bmtub3duLmRldmlhbmNlLm1lYW4sIHNkID0gaWRlYWwuZGV2aWFuY2Uuc2QsIGxvd2VyLnRhaWwgPSBUKQpgYGAKCjIpIFRocmVzaG9sZCBzZWxlY3Rpb24KYGBge3J9CnBsb3QodW5kaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZS5wLm11bHRpLCB1bmRpZmZlcmVudGlhdGVkLnFwJGRldmlhbmNlLnApCmFibGluZSh2ID0gMC4wNSwgY29sID0gInJlZCIpCmFibGluZShoID0gMC4wNSwgY29sID0gImJsdWUiKQoKcGxvdCh1bmRpZmZlcmVudGlhdGVkLnFwJGRldmlhbmNlLnAudW5rbm93biwgdW5kaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZS5wLm11bHRpKQphYmxpbmUoaCA9IDAuMDUsIGNvbCA9ICJibHVlIikKCnBsb3QoZGlmZmVyZW50aWF0ZWQucXAkZGV2aWFuY2UucC5tdWx0aSwgZGlmZmVyZW50aWF0ZWQucXAkZGV2aWFuY2UucCkKYWJsaW5lKHYgPSAwLjA1LCBjb2wgPSAicmVkIikKYWJsaW5lKGggPSAwLjA1LCBjb2wgPSAiYmx1ZSIpCgpwbG90KGRpZmZlcmVudGlhdGVkLnFwJGRldmlhbmNlLnAudW5rbm93biwgZGlmZmVyZW50aWF0ZWQucXAkZGV2aWFuY2UucC5tdWx0aSkKYWJsaW5lKGggPSAwLjA1LCBjb2wgPSAiYmx1ZSIpCmBgYAoKMykgQ3JlYXRlIHRoZSBpbml0aWFsIGNsYXNzaWZpY2F0aW9uIGRhdGEgZnJhbWUKYGBge3J9CiMjIGluaXRpYWwgY2xhc3MgZGF0YSBmcmFtZSBmb3IgdGhlIGRhdGEgKFVuZGlmZmVyZW50aWF0ZWQpCmluaXQuY2xhc3MudW5kaWZmIDwtIGRhdGEuZnJhbWUoY2VsbC5iYyA9IHJvd25hbWVzKHVuZGlmZmVyZW50aWF0ZWQucXApLCBpbml0LmNsYXNzID0gIlNpbmdsZS1JRCIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpyb3duYW1lcyhpbml0LmNsYXNzLnVuZGlmZikgPC0gaW5pdC5jbGFzcy51bmRpZmYkY2VsbC5iYwppbml0LmNsYXNzLnVuZGlmZltyb3duYW1lcyh1bmRpZmZlcmVudGlhdGVkLnFwW3doaWNoKHVuZGlmZmVyZW50aWF0ZWQucXAkZGV2aWFuY2UucCA+PSAwLjA1ICYgdW5kaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZS5wLm11bHRpID49IDAuOTUpLCBdKSwgImluaXQuY2xhc3MiXSA8LSAiU2luZ2xlLUlEIgppbml0LmNsYXNzLnVuZGlmZltyb3duYW1lcyh1bmRpZmZlcmVudGlhdGVkLnFwW3doaWNoKHVuZGlmZmVyZW50aWF0ZWQucXAkZGV2aWFuY2UucC5tdWx0aSA+PSAwICYgdW5kaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZS5wLnVua25vd24gPiAwLjk1ICYgdW5kaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZS5wIDwgMC4wNSksIF0pLCAiaW5pdC5jbGFzcyJdIDwtICJNdWx0aV9JRCIKaW5pdC5jbGFzcy51bmRpZmZbcm93bmFtZXModW5kaWZmZXJlbnRpYXRlZC5xcFt3aGljaCh1bmRpZmZlcmVudGlhdGVkLnFwJGRldmlhbmNlLnAubXVsdGkgPCAwLjA1ICYgdW5kaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZS5wLnVua25vd24gPj0gMCksIF0pLCAiaW5pdC5jbGFzcyJdIDwtICJVbmtub3duIgoKIyMgaW5pdGlhbCBjbGFzcyBkYXRhIGZyYW1lIGZvciB0aGUgZGF0YSAoRGlmZmVyZW50aWF0ZWQpCmluaXQuY2xhc3MuZGlmZiA8LSBkYXRhLmZyYW1lKGNlbGwuYmMgPSByb3duYW1lcyhkaWZmZXJlbnRpYXRlZC5xcCksIGluaXQuY2xhc3MgPSAiU2luZ2xlLUlEIiwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnJvd25hbWVzKGluaXQuY2xhc3MuZGlmZikgPC0gaW5pdC5jbGFzcy5kaWZmJGNlbGwuYmMKaW5pdC5jbGFzcy5kaWZmW3Jvd25hbWVzKGRpZmZlcmVudGlhdGVkLnFwW3doaWNoKGRpZmZlcmVudGlhdGVkLnFwJGRldmlhbmNlLnAgPj0gMC4wNSAmIGRpZmZlcmVudGlhdGVkLnFwJGRldmlhbmNlLnAubXVsdGkgPj0gMC45NSksIF0pLCAiaW5pdC5jbGFzcyJdIDwtICJTaW5nbGUtSUQiCmluaXQuY2xhc3MuZGlmZltyb3duYW1lcyhkaWZmZXJlbnRpYXRlZC5xcFt3aGljaChkaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZS5wLm11bHRpID49IDAgJiBkaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZS5wLnVua25vd24gPiAwLjk1ICYgZGlmZmVyZW50aWF0ZWQucXAkZGV2aWFuY2UucCA8IDAuMDUpLCBdKSwgImluaXQuY2xhc3MiXSA8LSAiTXVsdGlfSUQiCmluaXQuY2xhc3MuZGlmZltyb3duYW1lcyhkaWZmZXJlbnRpYXRlZC5xcFt3aGljaChkaWZmZXJlbnRpYXRlZC5xcCRkZXZpYW5jZS5wLm11bHRpIDwgMC4wNSAmIGRpZmZlcmVudGlhdGVkLnFwJGRldmlhbmNlLnAudW5rbm93biA+IDApLCBdKSwgImluaXQuY2xhc3MiXSA8LSAiVW5rbm93biIKYGBgCgojIyMjIEJpbmFyaXphdGlvbiBhbmQgQ2xhc3NpZmljYXRpb24KV2UgZ2VuZXJhdGUgdGhlIGJpbmFyaXphdGlvbiBtYXRyaXggc28gdGhhdCB1bmtub3ducyBhcmUgbGFiZWxsZWQgJzAnIGFuZCBrbm93biBjZWxsIHR5cGVzIGFyZSBsYWJlbGxlZCAnMScsIGFuZCBwZXJmb3JtIGNsYXNzaWZpY2F0aW9uIGJhc2VkIG9uIHRoZSBiaW5hcml6ZWQgY291bnQuIER1ZSB0byB0aGUgbGFyZ2UgY2VsbCBudW1iZXIsIHdlIGRpcmVjdGx5IGxvYWQgdGhlIGJpbmFyaXphdGlvbiBjb3VudHMgYW5kIGNsYXNzaWZpY2F0aW9ucy4KCjEpIEJpbmFyaXphdGlvbgpgYGB7ciwgZXZhbD1GQUxTRX0KY29sLnN1YiA8LSBuY29sKHJlZi5xcCkgLSAyCmJpbi5jb3VudC51bmRpZmYgPC0gYmluYXJpemF0aW9uLm1hbm4ud2hpdG5leShtdHggPSB1bmRpZmZlcmVudGlhdGVkLnFwWyxjKDE6Y29sLnN1YildLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWYucGVyYy5scyA9IHJlZi5wZXJtLmxzW1sxXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZi5tZXRhID0gcmVmLm1ldGEubHNrLCBwZXJjLmxzID0gcGVybS51bmRpZmYuYWxsW1sxXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluaXQuY2xhc3MgPSBpbml0LmNsYXNzLnVuZGlmZikKCmJpbi5jb3VudC5kaWZmIDwtIGJpbmFyaXphdGlvbi5tYW5uLndoaXRuZXkobXR4ID0gZGlmZmVyZW50aWF0ZWQucXBbLGMoMTpjb2wuc3ViKV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZi5wZXJjLmxzID0gcmVmLnBlcm0ubHNbWzFdXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVmLm1ldGEgPSByZWYubWV0YS5sc2ssIHBlcmMubHMgPSBwZXJtLmRpZmYuYWxsW1sxXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5pdC5jbGFzcyA9IGluaXQuY2xhc3MuZGlmZikKYGBgCgpgYGB7cn0KY29sLnN1YiA8LSBuY29sKHJlZi5xcCkgLSAyCmJpbi5jb3VudC51bmRpZmYgPC0gcmVhZFJEUygifi9EZXNrdG9wL1JlcHJvZHVjaWJpbGl0eS9GaWd1cmUgMy9JbnRlcm1lZGlhdGVzL0JpbmFyeV9Db3VudHNfYW5kX0NsYXNzaWZpY2F0aW9ucy91bmRpZmZlcmVudGlhdGVkX2JpbmFyeV9jb3VudHMuUmRzIikKYmluLmNvdW50LmRpZmYgPC0gcmVhZFJEUygifi9EZXNrdG9wL1JlcHJvZHVjaWJpbGl0eS9GaWd1cmUgMy9JbnRlcm1lZGlhdGVzL0JpbmFyeV9Db3VudHNfYW5kX0NsYXNzaWZpY2F0aW9ucy9kaWZmZXJlbnRpYXRlZF9iaW5hcnlfY291bnRzLlJkcyIpCmBgYAoKMikgQ2xhc3NpZmljYXRpb24KYGBge3IsIGV2YWw9RkFMU0V9CmNsYXNzaWZpY2F0aW9uLnVuZGlmZiA8LSBiaW5hcnkudG8uY2xhc3NpZmljYXRpb24oYmluLmNvdW50LnVuZGlmZlssYygxOmNvbC5zdWIpXSkKcm93bmFtZXMoY2xhc3NpZmljYXRpb24udW5kaWZmKSA8LSBjbGFzc2lmaWNhdGlvbi51bmRpZmYkYmFyY29kZQpjbGFzc2lmaWNhdGlvbi5kaWZmIDwtIGJpbmFyeS50by5jbGFzc2lmaWNhdGlvbihiaW4uY291bnQuZGlmZlssYygxOmNvbC5zdWIpXSkKcm93bmFtZXMoY2xhc3NpZmljYXRpb24uZGlmZikgPC0gY2xhc3NpZmljYXRpb24uZGlmZiRiYXJjb2RlCmBgYAoKYGBge3J9CmNsYXNzaWZpY2F0aW9uLnVuZGlmZiA8LSByZWFkUkRTKCJ+L0Rlc2t0b3AvUmVwcm9kdWNpYmlsaXR5L0ZpZ3VyZSAzL0ludGVybWVkaWF0ZXMvQmluYXJ5X0NvdW50c19hbmRfQ2xhc3NpZmljYXRpb25zL1VuZGlmZmVyZW50aWF0ZWRfY2xhc3MuUmRzIikKY2xhc3NpZmljYXRpb24uZGlmZiA8LSByZWFkUkRTKCJ+L0Rlc2t0b3AvUmVwcm9kdWNpYmlsaXR5L0ZpZ3VyZSAzL0ludGVybWVkaWF0ZXMvQmluYXJ5X0NvdW50c19hbmRfQ2xhc3NpZmljYXRpb25zL0RpZmZlcmVudGlhdGVkX2NsYXNzLlJkcyIpCmBgYAoKVG8gdGhpcyBlbmQsIHdlIGhhdmUgZmluaXNoZWQgY2xhc3NpZmljYXRpb24gb2YgdGhlIGNlbGxzIGluIHRoaXMgZGF0YXNldC4gTmV4dCwgd2UgY2hlY2sgdGhlIGNsYXNzaWZpY2F0aW9uIHJlc3VsdCBhZ2FpbnN0IHRoZSBjZWxsIHR5cGUgYW5ub3RhdGlvbiBmcm9tIExBUlJZIHRvIGNoZWNrIHRoZSBhY2N1cmFjeSBvZiBjbGFzc2lmaWNhdGlvbiBhbmQgZmlkZWxpdHkgb2YgdGhlIHJlZmVyZW5jZS4KCiMjIyMgQ2xhc3NpZmljYXRpb24gQ2hlY2sKMSkgV2UgcHV0IHRoZSBhY3R1YWwgbGFiZWwgaW4gdGhlIGNsYXNzaWZpY2F0aW9uIGRhdGEgZnJhbWUgYW5kIGNvbXBhcmUuCmBgYHtyfQpjbGFzc2lmaWNhdGlvbi51bmRpZmYkYWN0dWFsIDwtIGxza1tyb3duYW1lcyhjbGFzc2lmaWNhdGlvbi51bmRpZmYpLCAiQ2VsbC50eXBlLmFubm90YXRpb24iXQpjbGFzc2lmaWNhdGlvbi51bmRpZmYkdGltZXBvaW50IDwtIG1ldGEubGFycnlbcm93bmFtZXMoY2xhc3NpZmljYXRpb24udW5kaWZmKSwgIlRpbWUucG9pbnQiXQp0YWJsZShjbGFzc2lmaWNhdGlvbi51bmRpZmYkY2FsbCwgY2xhc3NpZmljYXRpb24udW5kaWZmJHRpbWVwb2ludCkKCmNsYXNzaWZpY2F0aW9uLmRpZmYkYWN0dWFsIDwtIGxza1tyb3duYW1lcyhjbGFzc2lmaWNhdGlvbi5kaWZmKSwgIkNlbGwudHlwZS5hbm5vdGF0aW9uIl0KY2xhc3NpZmljYXRpb24uZGlmZiR0aW1lcG9pbnQgPC0gbWV0YS5sYXJyeVtyb3duYW1lcyhjbGFzc2lmaWNhdGlvbi5kaWZmKSwgIlRpbWUucG9pbnQiXQp0YWJsZShjbGFzc2lmaWNhdGlvbi5kaWZmJGNhbGwsIGNsYXNzaWZpY2F0aW9uLmRpZmYkdGltZXBvaW50KQp0YWJsZShjbGFzc2lmaWNhdGlvbi5kaWZmJGNhbGwsIGNsYXNzaWZpY2F0aW9uLmRpZmYkYWN0dWFsKQpgYGAKCjIpIFdlIHBsb3QgdGhlIG5ldyBjbGFzc2lmaWNhdGlvbiBmb3IgdGhlIHVuZGlmZmVyZW50aWF0ZWQgY2VsbHMKYGBge3J9Cmxzay51bmRpZmYgPC0gbHNrW3doaWNoKGxzayRDZWxsLnR5cGUuYW5ub3RhdGlvbiA9PSAiVW5kaWZmZXJlbnRpYXRlZCIpLF0KbHNrLnVuZGlmZiRjYXB5LmNhbGwgPC0gY2xhc3NpZmljYXRpb24udW5kaWZmW3Jvd25hbWVzKGxzay51bmRpZmYpLCAiY2FsbCJdCgpsc2suZGlmZiA8LSBsc2tbd2hpY2gobHNrJENlbGwudHlwZS5hbm5vdGF0aW9uICE9ICJVbmRpZmZlcmVudGlhdGVkIiksXQpsc2suZGlmZiRjYXB5LmNhbGwgPC0gY2xhc3NpZmljYXRpb24uZGlmZltyb3duYW1lcyhsc2suZGlmZiksICJjYWxsIl0KCmdncGxvdChsc2ssIGFlcyh4ID0gU1BSSU5HLngsIHkgPSBTUFJJTkcueSkpICsKICBnZW9tX3BvaW50KGNvbG9yID0gImdyZXkiKSArCiAgZ2VvbV9wb2ludChkYXRhID0gbHNrLnVuZGlmZlt3aGljaChsc2sudW5kaWZmJGNhcHkuY2FsbCAlaW4lIGMoIk11bHRpX0lEIikpLF0sIG1hcHBpbmcgPSBhZXMoeCA9IFNQUklORy54LCB5ID0gU1BSSU5HLnksIGNvbG9yID0gY2FweS5jYWxsKSkKCmdncGxvdChsc2ssIGFlcyh4ID0gU1BSSU5HLngsIHkgPSBTUFJJTkcueSkpICsKICBnZW9tX3BvaW50KGNvbG9yID0gImdyZXkiKSArCiAgZ2VvbV9wb2ludChkYXRhID0gbHNrLnVuZGlmZlt3aGljaChsc2sudW5kaWZmJGNhcHkuY2FsbCAlaW4lIGMoIlVua25vd24iKSksXSwgbWFwcGluZyA9IGFlcyh4ID0gU1BSSU5HLngsIHkgPSBTUFJJTkcueSwgY29sb3IgPSBjYXB5LmNhbGwpKQoKZ2dwbG90KGxzaywgYWVzKHggPSBTUFJJTkcueCwgeSA9IFNQUklORy55KSkgKwogIGdlb21fcG9pbnQoY29sb3IgPSAiZ3JleSIpICsKICBnZW9tX3BvaW50KGRhdGEgPSBsc2sudW5kaWZmWy13aGljaChsc2sudW5kaWZmJGNhcHkuY2FsbCAlaW4lIGMoIkVyeXRocm9pZCIsICJMeW1waG9pZCIsICJNdWx0aV9JRCIsICJVbmtub3duIikpLF0sIG1hcHBpbmcgPSBhZXMoeCA9IFNQUklORy54LCB5ID0gU1BSSU5HLnksIGNvbG9yID0gY2FweS5jYWxsKSkKYGBgCgozKSBXZSBwbG90IHRoZSBuZXcgY2xhc3NpZmljYXRpb24gZm9yIHRoZSBkaWZmZXJlbnRpYXRlZCBjZWxscy4gCmBgYHtyfQpnZ3Bsb3QobHNrLCBhZXMoeCA9IFNQUklORy54LCB5ID0gU1BSSU5HLnkpKSArCiAgZ2VvbV9wb2ludChjb2xvciA9ICJncmV5IikgKwogIGdlb21fcG9pbnQoZGF0YSA9IGxzay5kaWZmW3doaWNoKGxzay5kaWZmJGNhcHkuY2FsbCAlaW4lIGMoIk11bHRpX0lEIikpLF0sIG1hcHBpbmcgPSBhZXMoeCA9IFNQUklORy54LCB5ID0gU1BSSU5HLnksIGNvbG9yID0gY2FweS5jYWxsKSkKCmdncGxvdChsc2ssIGFlcyh4ID0gU1BSSU5HLngsIHkgPSBTUFJJTkcueSkpICsKICBnZW9tX3BvaW50KGNvbG9yID0gImdyZXkiKSArCiAgZ2VvbV9wb2ludChkYXRhID0gbHNrLmRpZmZbd2hpY2gobHNrLmRpZmYkY2FweS5jYWxsICVpbiUgYygiVW5rbm93biIpKSxdLCBtYXBwaW5nID0gYWVzKHggPSBTUFJJTkcueCwgeSA9IFNQUklORy55LCBjb2xvciA9IGNhcHkuY2FsbCkpCgpnZ3Bsb3QobHNrLCBhZXMoeCA9IFNQUklORy54LCB5ID0gU1BSSU5HLnkpKSArCiAgZ2VvbV9wb2ludChjb2xvciA9ICJncmV5IikgKwogIGdlb21fcG9pbnQoZGF0YSA9IGxzay5kaWZmWy13aGljaChsc2suZGlmZiRjYXB5LmNhbGwgJWluJSBjKCJFcnl0aHJvaWQiLCAiTHltcGhvaWQiLCAiTXVsdGlfSUQiLCAiVW5rbm93biIpKSxdLCBtYXBwaW5nID0gYWVzKHggPSBTUFJJTkcueCwgeSA9IFNQUklORy55LCBjb2xvciA9IGNhcHkuY2FsbCkpCmBgYAoKNCkgV2UgY29tcGFyZSBvdXIgYW5ub3RhdGlvbiBvZiB0aGUgZGlmZmVyZW50aWF0ZWQgY2VsbHMgdG8gdGhlIGNlbGwgdHlwZSBhbm5vdGF0aW9uIGZyb20gTEFSUlkgdmlhIGhlYXRtYXAuIFRoZSBjb2xvciBvZiBlYWNoIGJsb2NrIHJlcHJlc2VudHMgcGVyY2VudGFnZXMuIEhpZ2hlciBwZXJjZW50YWdlID0gbGlnaHRlciBjb2xvci4KYGBge3J9CmhlYXRtYXAudG8ucGxvdCA8LSBhcy5kYXRhLmZyYW1lKGFwcGx5KHRhYmxlKGNsYXNzaWZpY2F0aW9uLmRpZmYkY2FsbCwgY2xhc3NpZmljYXRpb24uZGlmZiRhY3R1YWwpLCAyLCBmdW5jdGlvbih4KSByb3VuZCh4KjEwMC9zdW0oeCksIGRpZ2l0cyA9IDMpKSkKCmhlYXRtYXAudG8ucGxvdCRjYXB5LmNhbGwgPC0gcm93bmFtZXMoaGVhdG1hcC50by5wbG90KQpoZWF0bWFwLnRvLnBsb3QubWVsdCA8LSByZXNoYXBlMjo6bWVsdChoZWF0bWFwLnRvLnBsb3QpCmBgYAoKYGBge3IsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTYuNX0KaGVhdG1hcC50by5wbG90Lm1lbHQkY2FweS5jYWxsIDwtIGZhY3RvcihoZWF0bWFwLnRvLnBsb3QubWVsdCRjYXB5LmNhbGwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiQmFzbyIsICJFb3MiLCAiTWFzdCIsICJNb25vY3l0ZSIsICJOZXV0cm9waGlsIiwgIlVua25vd24iLCAiTXVsdGlfSUQiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlcmVkID0gVCkKaGVhdG1hcC50by5wbG90Lm1lbHQkdmFyaWFibGUgPC0gZmFjdG9yKGhlYXRtYXAudG8ucGxvdC5tZWx0JHZhcmlhYmxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkJhc28iLCAiRW9zIiwgIk1hc3QiLCAiTW9ub2N5dGUiLCAiTmV1dHJvcGhpbCIsICJNZWciLCAiRXJ5dGhyb2lkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJMeW1waG9pZCIsICJwREMiLCAiQ2NyN19EQyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyZWQgPSBUKQpnZ3Bsb3QoaGVhdG1hcC50by5wbG90Lm1lbHQsIGFlcyh4ID0gY2FweS5jYWxsLCB5ID0gdmFyaWFibGUsIGZpbGwgPSB2YWx1ZSkpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gIkEiLCBiZWdpbiA9IDAuMTUsIGVuZCA9IDAuODUpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxMiwgYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTIpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQuaXRhbGljIiwgc2l6ZSA9IDE0KSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkLml0YWxpYyIsIHNpemUgPSAxNCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICB0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQuaXRhbGljIiwgc2l6ZSA9IDE0KSwKICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiksCiAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgClRvIHRoaXMgZW5kLCB3ZSBoYXZlIGNoZWNrZWQgdGhlIGFjY3VyYWN5IG9mIGNsYXNzaWZpY2F0aW9uLiBTcGVjaWZpY2FsbHksIGNlbGwgdHlwZXMgdGhhdCBkaWQgbm90IGNvbnRyaWJ1dGUgdG8gcmVmZXJlbmNlIG1ha2luZyBtYWlubHkgY29udHJpYnV0ZSB0byB0aGUgdW5rbm93biBwb3B1bGF0aW9uIGluIHRoZSBjbGFzc2lmaWNhdGlvbiBvZiBkaWZmZXJlbnRpYXRlZCBjZWxscy4gV2UgbmV4dCBjaGVjayB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBoeWJyaWQgY2VsbHMgYmVmb3JlIG1vdmluZyBpbnRvIGFzc2Vzc2luZyBsaW5lYWdlLgoKIyMjIyBIeWJyaWQgY2VsbHMKRm9yIGZ1cnRoZXIgZG93bnN0cmVhbSBhbmFseXNpcywgd2UgcmVmb3JtYXRlZCB0aGUgaHlicmlkIGNlbGxzIGFuZCBwdXQgdGhlbSBpbnRvIGEgZGF0YSBmcmFtZSB3aXRoIHRoZSBkZXRhaWxlZCBkaXNjcmV0ZSByZXByZXNlbnRhdGlvbnMgb2YgdGhlIGh5YnJpZHMuCgoxKSBIZXJlIHdlIGZpcnN0IHB1dCB0aGUgZGV0YWlsZWQgYnJlYWtkb3ducyBvZiB0aGUgaHlicmlkIGNlbGxzIGludG8gYSBkYXRhIGZyYW1lCmBgYHtyfQphbGwuYmluLmNvdW50IDwtIHJiaW5kKGJpbi5jb3VudC5kaWZmLCBiaW4uY291bnQudW5kaWZmKQphbGwuY2xhc3NpZmljYXRpb24gPC0gcmJpbmQoY2xhc3NpZmljYXRpb24uZGlmZiwgY2xhc3NpZmljYXRpb24udW5kaWZmKQptdWx0aS5pZC5jZWxscyA8LSBhbGwuY2xhc3NpZmljYXRpb24kYmFyY29kZVt3aGljaChhbGwuY2xhc3NpZmljYXRpb24kY2FsbCA9PSAiTXVsdGlfSUQiKV0KCmJpbmFyeS5tLmlkIDwtIGFzLmRhdGEuZnJhbWUoYWxsLmJpbi5jb3VudFttdWx0aS5pZC5jZWxscywgXSkKYmluYXJ5Lm0uaWQkY2VsbC5pZCA8LSByb3duYW1lcyhiaW5hcnkubS5pZCkKYmluYXJ5Lm0uaWQubWVsdCA8LSByZXNoYXBlMjo6bWVsdChiaW5hcnkubS5pZCkKYmluYXJ5Lm0uaWQubWVsdCA8LSBiaW5hcnkubS5pZC5tZWx0W3doaWNoKGJpbmFyeS5tLmlkLm1lbHQkdmFsdWUgPiAwKSwgXQoKYmluYXJ5Lm5ldy4yIDwtIGRhdGEuZnJhbWUoKQpiaW5hcnkubS5pZC5tZWx0JHZhcmlhYmxlIDwtIGFzLmNoYXJhY3RlcihiaW5hcnkubS5pZC5tZWx0JHZhcmlhYmxlKQpmb3IgKG1jIGluIG11bHRpLmlkLmNlbGxzKSB7CiAgY3Vyci5zdWIgPC0gYmluYXJ5Lm0uaWQubWVsdFt3aGljaChiaW5hcnkubS5pZC5tZWx0JGNlbGwuaWQgPT0gbWMpLCBdCiAgY3Vyci5jZWxsLnR5cGUuY29tYmluZSA8LSBwYXN0ZTAoZ3N1YihwYXR0ZXJuID0gImZyeG5fY2VsbC50eXBlXyIsIHJlcGxhY2VtZW50ID0gIiIsIHggPSBjdXJyLnN1YiR2YXJpYWJsZSksIGNvbGxhcHNlID0gIi0iKQogIAogIGN1cnIuZGYgPC0gZGF0YS5mcmFtZShjZWxsID0gbWMsIGNlbGwudHlwZSA9IGN1cnIuY2VsbC50eXBlLmNvbWJpbmUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQogIGlmIChucm93KGJpbmFyeS5uZXcuMikgPD0gMCl7CiAgICBiaW5hcnkubmV3LjIgPC0gY3Vyci5kZgogIH0gZWxzZSB7CiAgICBiaW5hcnkubmV3LjIgPC0gcmJpbmQoYmluYXJ5Lm5ldy4yLCBjdXJyLmRmKQogIH0KfQpiaW5hcnkubmV3LjIuZDIgPC0gYmluYXJ5Lm5ldy4yCnJvd25hbWVzKGJpbmFyeS5uZXcuMi5kMikgPC0gYmluYXJ5Lm5ldy4yLmQyJGNlbGwKYmluYXJ5Lm5ldy4yLmQyJHRwIDwtIGxza1tyb3duYW1lcyhiaW5hcnkubmV3LjIuZDIpLCAiVGltZS5wb2ludCJdCmBgYAoKMikgV2UgYXNzZXNzIHRoZSBtYWpvciBoeWJyaWQgcG9wdWxhdGlvbnMgaGVyZS4KYGBge3J9Cmh5YnJpZC5mcmVxLnRhYmxlIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoYmluYXJ5Lm5ldy4yLmQyJGNlbGwudHlwZSkgKiAxMDAvc3VtKHRhYmxlKGJpbmFyeS5uZXcuMi5kMiRjZWxsLnR5cGUpKSkKaHlicmlkLmZyZXEudGFibGUuc29ydCA8LSBoeWJyaWQuZnJlcS50YWJsZVtvcmRlcigtaHlicmlkLmZyZXEudGFibGUkRnJlcSksIF0KCmh5YnJpZC5mcmVxLnRhYmxlLnNvcnQkVmFyMSA8LSBmYWN0b3IoaHlicmlkLmZyZXEudGFibGUuc29ydCRWYXIxLCBsZXZlbHMgPSBoeWJyaWQuZnJlcS50YWJsZS5zb3J0JFZhcjEsIG9yZGVyZWQgPSBUKQoKZ2dwbG90KGh5YnJpZC5mcmVxLnRhYmxlLnNvcnQsIGFlcyh4ID0gVmFyMSwgeSA9IEZyZXEsIGZpbGwgPSBWYXIxKSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAiQSIsIGJlZ2luID0gMC4xNSwgZW5kID0gMC44NSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTIsIGFuZ2xlID0gOTAsIGhqdXN0ID0gMSksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDEyKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZC5pdGFsaWMiLCBzaXplID0gMTQpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkLml0YWxpYyIsIHNpemUgPSAxNCksCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJibGFjayIpLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpPdmVyYWxsLCB3ZSBpZGVudGlmaWVkIHRoZSBtYWpvciBoeWJyaWQgcG9wdWxhdGlvbnMuIFdlIGludmVzdGlnYXRlIHRoZSBwb3RlbnRpYWwgb2YgdGhlc2UgaHlicmlkIGNlbGxzIHVzaW5nIHRoZSBsaW5lYWdlIHRyYWNpbmcgZGF0YS4KCiMjIyBDbG9uYWwgaW5mb3JtYXRpb24KSGVyZSB3ZSBsb2FkIHRoZSBjbG9uYWwgaW5mb3JtYXRpb24gZnJvbSBXZWlucmViIGV0IGFsLiBhbmQgcHJlcHJvY2VzcyBpdCBpbiB0aGUgcHJvcGVyIGZvcm1hdC4gV2UgaWRlbnRpZnkgdGhlIHN0YXRlLWZhdGUgY2xvbmVzIGFuZCBzdGF0ZS1zdGF0ZSBjbG9uZXMuCgoxKSBMb2FkIGNsb25hbCBpbmZvcm1hdGlvbiAmIGlkZW50aWZ5IHN0YXRlLWZhdGUgY2xvbmVzIGFuZCBzdGF0ZS1zdGF0ZSBjbG9uZXMKYGBge3J9CmxpYnJhcnkoTWF0cml4KQojIyByZWFkIGluIGNsb25lIG1hdHJpeCBhbmQgcmVmb3JtIHRoZSBtYXRyaXggdG8gY2xvbmUgYnkgY2VsbApjbG9uZS5hbm5vPC1yZWFkTU0oIn4vRGVza3RvcC9SZXByb2R1Y2liaWxpdHkvRmlndXJlIDMvSW50ZXJtZWRpYXRlcy9MQVJSWSBTdGF0ZSBGYXRlIERhdGEvc3RhdGVGYXRlX2luVml0cm9fY2xvbmVfbWF0cml4Lm10eCIpCmNsb25lLmFubm88LXQoY2xvbmUuYW5ubykKIyMgcmVhZCBpbiBtZXRhCm1ldGE8LXJlYWQudGFibGUoIn4vRGVza3RvcC9SZXByb2R1Y2liaWxpdHkvRmlndXJlIDMvSW50ZXJtZWRpYXRlcy9MQVJSWSBTdGF0ZSBGYXRlIERhdGEvc3RhdGVGYXRlX2luVml0cm9fbWV0YWRhdGEudHh0IixoZWFkZXIgPSBULHNlcD0iXHQiKQojIyBmb3IgZWFjaCBjbG9uZSwgZmluZCBjZWxsIGluZGV4Cm1ldGEuY2VsbC5pbmQ8LWFwcGx5KGNsb25lLmFubm8sMSwgZnVuY3Rpb24oeCkgd2hpY2goeCE9MCkpCiMjIGZpbmQgbXVsdGktY2VsbCBjbG9uZXMKbXVsdGkuY2VsbC5jbG9uZTwtc2FwcGx5KG1ldGEuY2VsbC5pbmQsIGZ1bmN0aW9uKHgpIGxlbmd0aCh4KT4xKQojIyBmaW5kIGNlbGwgaW5kZXggZm9yIGVhY2ggbXVsdGktY2VsbCBjbG9uZQptdWx0aS5jZWxsLmluZDwtYXBwbHkoY2xvbmUuYW5ub1t3aGljaChtdWx0aS5jZWxsLmNsb25lPT1UKSxdLDEsIGZ1bmN0aW9uKHgpIHdoaWNoKHghPTApKQojIyBmaW5kIGRldiB0aW1lIGZvciBjZWxscwpjbG9uZS50aW1lPC1zYXBwbHkobXVsdGkuY2VsbC5pbmQsIGZ1bmN0aW9uKHgpIG1ldGEkVGltZS5wb2ludFt4XSkKCiMjIGZpbmQgc3RhdGUtZmF0ZSBjbG9uZXMKc3RhdGUuZmF0ZS5jbG9uZTwtc2FwcGx5KGNsb25lLnRpbWUsIGZ1bmN0aW9uKHgpIGxlbmd0aCh1bmlxdWUoeCkpIT0xJiYyJWluJXVuaXF1ZSh4KSkKc3RhdGUuZmF0ZS5jbG9uZS5pbmRleDwtd2hpY2goc3RhdGUuZmF0ZS5jbG9uZT09VCkKCiMjIGZpbmQgc3RhdGUtc3RhdGUgY2xvbmVzCnN0YXRlLnN0YXRlLmNsb25lPC1zYXBwbHkoY2xvbmUudGltZSwgZnVuY3Rpb24oeCkgc3VtKHg9PTIpPj0yKQpzdGF0ZS5zdGF0ZS5jbG9uZS5pbmRleDwtd2hpY2goc3RhdGUuc3RhdGUuY2xvbmU9PVQpCmBgYAoKMikgRnVydGhlciBmb3JtYXR0aW5nIG9mIHRoZSBkYXRhIGZyYW1lIGFuZCBhZGQgdGltZSBwb2ludCBpbmZvcm1hdGlvbgpgYGB7cn0KY2xvbmUuYW5ubzwtcmVhZE1NKCJ+L0Rlc2t0b3AvUmVwcm9kdWNpYmlsaXR5L0ZpZ3VyZSAzL0ludGVybWVkaWF0ZXMvTEFSUlkgU3RhdGUgRmF0ZSBEYXRhL3N0YXRlRmF0ZV9pblZpdHJvX2Nsb25lX21hdHJpeC5tdHgiKQpyb3duYW1lcyhjbG9uZS5hbm5vKTwtcGFzdGUoIkNlbGxfIiwxOjEzMDg4NyxzZXAgPSAiIikKY29sbmFtZXMoY2xvbmUuYW5ubyk8LXBhc3RlKCJDbG9uZV8iLDE6NTg2NCxzZXAgPSAiIikKCnJvd25hbWVzKG1ldGEpIDwtIHBhc3RlKCJDZWxsXyIsMToxMzA4ODcsc2VwID0gIiIpCgpjbG9uZS5hbm5vLnN0YXRlLmZhdGUgPC0gYXMuZGF0YS5mcmFtZShNYXRyaXg6OnN1bW1hcnkoY2xvbmUuYW5ub1ssIHN0YXRlLmZhdGUuY2xvbmUuaW5kZXhdKSkKY2xvbmUuYW5uby5zdGF0ZS5zdGF0ZSA8LSBhcy5kYXRhLmZyYW1lKE1hdHJpeDo6c3VtbWFyeShjbG9uZS5hbm5vWywgc3RhdGUuc3RhdGUuY2xvbmUuaW5kZXhdKSkKCmNsb25lLmFubm8uc3RhdGUuZmF0ZSRpIDwtIHBhc3RlKCJDZWxsXyIsIGNsb25lLmFubm8uc3RhdGUuZmF0ZSRpLCBzZXAgPSAiIikKY2xvbmUuYW5uby5zdGF0ZS5mYXRlJGogPC0gcGFzdGUoIkNsb25lXyIsIGNsb25lLmFubm8uc3RhdGUuZmF0ZSRqLCBzZXAgPSAiIikKCmNsb25lLmFubm8uc3RhdGUuc3RhdGUkaSA8LSBwYXN0ZSgiQ2VsbF8iLCBjbG9uZS5hbm5vLnN0YXRlLnN0YXRlJGksIHNlcCA9ICIiKQpjbG9uZS5hbm5vLnN0YXRlLnN0YXRlJGogPC0gcGFzdGUoIkNsb25lXyIsIGNsb25lLmFubm8uc3RhdGUuc3RhdGUkaiwgc2VwID0gIiIpCgpjbG9uZS5hbm5vLnN0YXRlLmZhdGUkdHAgPC0gbWV0YVtjbG9uZS5hbm5vLnN0YXRlLmZhdGUkaSwgIlRpbWUucG9pbnQiXQpjbG9uZS5hbm5vLnN0YXRlLnN0YXRlJHRwIDwtIG1ldGFbY2xvbmUuYW5uby5zdGF0ZS5zdGF0ZSRpLCAiVGltZS5wb2ludCJdCmBgYAoKMykgQWRkIHRoZSBjbGFzc2lmaWNhdGlvbiBpbmZvcm1hdGlvbiBpbnRvIHRoZSBjbG9uYWwgZGF0YSBhbmQgYWRkIHRoZSBjZWxsIHR5cGUgYW5ub3RhdGlvbiBmcm9tIExBUlJZIGludG8gdGhlIGNsb25hbCBkYXRhIGZyYW1lCmBgYHtyfQpyb3duYW1lcyhjbG9uZS5hbm5vLnN0YXRlLmZhdGUpIDwtIGNsb25lLmFubm8uc3RhdGUuZmF0ZSRpCnJvd25hbWVzKGNsb25lLmFubm8uc3RhdGUuc3RhdGUpIDwtIGNsb25lLmFubm8uc3RhdGUuc3RhdGUkaQoKY2xvbmUuYW5uby5zdGF0ZS5mYXRlJGNhcHkuY2FsbCA8LSBOQQoKZnVsbC5jbGFzc2lmaWNhdGlvbi50YWJsZSA8LSByYmluZChjbGFzc2lmaWNhdGlvbi51bmRpZmYsIGNsYXNzaWZpY2F0aW9uLmRpZmYpCmZ1bGwuY2xhc3MuaW50ZXJzZWN0LnNmIDwtIGludGVyc2VjdChyb3duYW1lcyhmdWxsLmNsYXNzaWZpY2F0aW9uLnRhYmxlKSwgcm93bmFtZXMoY2xvbmUuYW5uby5zdGF0ZS5mYXRlKSkKZnVsbC5jbGFzcy5pbnRlcnNlY3Quc3MgPC0gaW50ZXJzZWN0KHJvd25hbWVzKGZ1bGwuY2xhc3NpZmljYXRpb24udGFibGUpLCByb3duYW1lcyhjbG9uZS5hbm5vLnN0YXRlLnN0YXRlKSkKCmNsb25lLmFubm8uc3RhdGUuZmF0ZVtmdWxsLmNsYXNzLmludGVyc2VjdC5zZiwgImNhcHkuY2FsbCJdIDwtIGZ1bGwuY2xhc3NpZmljYXRpb24udGFibGVbZnVsbC5jbGFzcy5pbnRlcnNlY3Quc2YsICJjYWxsIl0KY2xvbmUuYW5uby5zdGF0ZS5zdGF0ZVtmdWxsLmNsYXNzLmludGVyc2VjdC5zcywgImNhcHkuY2FsbCJdIDwtIGZ1bGwuY2xhc3NpZmljYXRpb24udGFibGVbZnVsbC5jbGFzcy5pbnRlcnNlY3Quc3MsICJjYWxsIl0KCmNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYSA8LSBjbG9uZS5hbm5vLnN0YXRlLmZhdGVbIWlzLm5hKGNsb25lLmFubm8uc3RhdGUuZmF0ZSRjYXB5LmNhbGwpLCBdCmNsb25lLmFubm8uc3RhdGUuc3RhdGUubm8ubmEgPC0gY2xvbmUuYW5uby5zdGF0ZS5zdGF0ZVshaXMubmEoY2xvbmUuYW5uby5zdGF0ZS5zdGF0ZSRjYXB5LmNhbGwpLCBdCgpjbG9uZS5hbm5vLnN0YXRlLnN0YXRlLm5vLm5hJGFjdHVhbC5jYWxsIDwtIGxza1tyb3duYW1lcyhjbG9uZS5hbm5vLnN0YXRlLnN0YXRlLm5vLm5hKSwgIkNlbGwudHlwZS5hbm5vdGF0aW9uIl0KY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hJGFjdHVhbC5jYWxsIDwtIGxza1tyb3duYW1lcyhjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmEpLCAiQ2VsbC50eXBlLmFubm90YXRpb24iXQpgYGAKCjQpIEZpbGwgaW4gdGhlIGRldGFpbGVkIGJyZWFrZG93bnMgb2YgdGhlIGh5YnJpZCBjZWxscwpgYGB7cn0KbXVsdGlfaWQuc2YuY2xvbmVzIDwtIHVuaXF1ZShjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmEkalt3aGljaChjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmEkY2FweS5jYWxsID09ICJNdWx0aV9JRCIpXSkKCmNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYVssICJ3aXRoLm11bHRpLmxhYmVsIl0gPC0gYmluYXJ5Lm5ldy4yLmQyW2Nsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYSRpLCAiY2VsbC50eXBlIl0KY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hW3doaWNoKGlzLm5hKGNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYSR3aXRoLm11bHRpLmxhYmVsKSksICJ3aXRoLm11bHRpLmxhYmVsIl0gPC0gY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hJGNhcHkuY2FsbFt3aGljaChpcy5uYShjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmEkd2l0aC5tdWx0aS5sYWJlbCkpXQpgYGAKCjUpIEFzc2VzcyB0aGUgbGluZWFnZSByZWxhdGVkIHNpYmxpbmdzIG9mIHRoZSBoeWJyaWQgY2VsbHMuIFdlIGZpcnN0IGNvbXB1dGUgdGhlIGNsb25hbCBjb21wb3NpdGlvbiBvZiB0aGUgY2xvbmVzIHdoZXJlIHRoZSBoeWJyaWQgY2VsbHMgcmVzaWRlIGluIGEgZGF0YSBmcmFtZS4KYGBge3J9CmNsb25lcy5zdWIud2l0aC5uby51bmtub3duIDwtIGNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYVt3aGljaChjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmEkd2l0aC5tdWx0aS5sYWJlbCAhPSAiVW5rbm93biIpLCBdCmNsb25lcy5zcy5zdWIud2l0aC5uby51bmtub3duIDwtIGNsb25lLmFubm8uc3RhdGUuc3RhdGUubm8ubmFbd2hpY2goY2xvbmUuYW5uby5zdGF0ZS5zdGF0ZS5uby5uYSRjYXB5LmNhbGwgIT0gIlVua25vd24iKSwgXQpjbG9uZXMuc3Muc3ViLndpdGgubm8udW5rbm93biR3aXRoLm11bHRpLmxhYmVsIDwtIGNsb25lcy5zcy5zdWIud2l0aC5uby51bmtub3duJGNhcHkuY2FsbApjbG9uZXMuc3Muc3ViLndpdGgubm8udW5rbm93biRqIDwtIHBhc3RlMCgic3NfIiwgY2xvbmVzLnNzLnN1Yi53aXRoLm5vLnVua25vd24kaikKY2xvbmVzLnNzLnN1Yi53aXRoLm5vLnVua25vd25baW50ZXJzZWN0KHJvd25hbWVzKGNsb25lcy5zcy5zdWIud2l0aC5uby51bmtub3duKSwgcm93bmFtZXMoYmluYXJ5Lm5ldy4yLmQyKSksICJ3aXRoLm11bHRpLmxhYmVsIl0gPC0gYmluYXJ5Lm5ldy4yLmQyW2ludGVyc2VjdChyb3duYW1lcyhjbG9uZXMuc3Muc3ViLndpdGgubm8udW5rbm93biksIHJvd25hbWVzKGJpbmFyeS5uZXcuMi5kMikpLCAiY2VsbC50eXBlIl0KCmNsb25lcy5hZ2dyIDwtIHJiaW5kKGNsb25lcy5zdWIud2l0aC5uby51bmtub3duLCBjbG9uZXMuc3Muc3ViLndpdGgubm8udW5rbm93bikKbWVnLm5ldXRybyA8LSBjb21wdXRlLmNvbXBvc2l0aW9uLmNsb25lcyhjbG9uZXMuYWdnciwgIndpdGgubXVsdGkubGFiZWwiKQoKY2xvbmVzLndpdGgubXVsdGkgPC0gbWVnLm5ldXRyb1t3aGljaChtZWcubmV1dHJvJFZhcjEgJWluJSBjKCJEYXlfNF9Nb25vY3l0ZS1OZXV0cm9waGlsIiwgIkRheV82X01vbm9jeXRlLU5ldXRyb3BoaWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkRheV80X0Jhc28tTWFzdCIsICJEYXlfNl9CYXNvLUVvcyIpKSwgXQpyb3duYW1lcyhjbG9uZXMud2l0aC5tdWx0aSkgPC0gYXMuY2hhcmFjdGVyKGNsb25lcy53aXRoLm11bHRpJGNsb25lKQpjbG9uZXMud2l0aC5tdWx0aSRWYXIxIDwtIGFzLmNoYXJhY3RlcihjbG9uZXMud2l0aC5tdWx0aSRWYXIxKQoKbWVnLm5ldXRyby5zdWIgPC0gbWVnLm5ldXRyb1t3aGljaChtZWcubmV1dHJvJGNsb25lICVpbiUgY2xvbmVzLndpdGgubXVsdGkkY2xvbmUpLCBdCgptZWcubmV1dHJvLnN1YiRWYXIxIDwtIGFzLmNoYXJhY3RlcihtZWcubmV1dHJvLnN1YiRWYXIxKQptZWcubmV1dHJvLnN1YiRuZXcudmFyIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQobWVnLm5ldXRyby5zdWIkVmFyMSwgIl8iKSwgZnVuY3Rpb24oeCkgcGFzdGUwKHhbMzpsZW5ndGgoeCldLCBjb2xsYXBzZSA9ICItIikpKQoKbWVnLm5ldXRyby5zdWIkY2xvbmUubXVsdGkubGFiZWwgPC0gY2xvbmVzLndpdGgubXVsdGlbbWVnLm5ldXRyby5zdWIkY2xvbmUsICJWYXIxIl0KbWVnLm5ldXRyby5zdWIkY2xvbmUubXVsdGkubGFiZWwgPC0gZ3N1YigiRGF5XzRfIiwgIiIsIG1lZy5uZXV0cm8uc3ViJGNsb25lLm11bHRpLmxhYmVsKQptZWcubmV1dHJvLnN1YiRjbG9uZS5tdWx0aS5sYWJlbCA8LSBnc3ViKCJEYXlfNl8iLCAiIiwgbWVnLm5ldXRyby5zdWIkY2xvbmUubXVsdGkubGFiZWwpCgptZWcubmV1dHJvLmR0IDwtIHNldERUKG1lZy5uZXV0cm8uc3ViKQptZWcubmV1dHJvLm10eCA8LSBkY2FzdChtZWcubmV1dHJvLmR0LCBjbG9uZS5tdWx0aS5sYWJlbH5uZXcudmFyLCBmaWxsID0gMCwgdmFsdWUudmFyID0gIkZyZXEiLCBmdW4uYWdncmVnYXRlID0gbWVhbikKbWVnLm5ldXRyby5tdHgucmVjb2xsYXBzZSA8LSByZXNoYXBlMjo6bWVsdChtZWcubmV1dHJvLm10eCkKYGBgCgo2KSBXZSBuZXh0IHBsb3QgdGhlIHBlcmNlbnRhZ2Ugb2YgdGhlIGNvdW50ZXIgcGFydHMgb2YgdGhlc2UgaHlicmlkIGNlbGxzLgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NH0KbWVnLm5ldXRyby5tdHgucmVjb2xsYXBzZSR2YXJpYWJsZSA8LSBhcy5jaGFyYWN0ZXIobWVnLm5ldXRyby5tdHgucmVjb2xsYXBzZSR2YXJpYWJsZSkKCm1lZy5uZXV0cm8ubXR4LnJlY29sbGFwc2UkdmFyaWFibGUgPC0gZmFjdG9yKG1lZy5uZXV0cm8ubXR4LnJlY29sbGFwc2UkdmFyaWFibGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkJhc28iLCAiRW9zIiwgIk1hc3QiLCAiTW9ub2N5dGUiLCAiTmV1dHJvcGhpbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkJhc28tTWFzdCIsICJNb25vY3l0ZS1OZXV0cm9waGlsIiwgIkJhc28tRW9zIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyZWQgPSBUKQoKbWVnLm5ldXRyby5tdHgucmVjb2xsYXBzZSRjbG9uZS5tdWx0aS5sYWJlbCA8LSBmYWN0b3IobWVnLm5ldXRyby5tdHgucmVjb2xsYXBzZSRjbG9uZS5tdWx0aS5sYWJlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiQmFzby1Fb3MiLCAiQmFzby1NYXN0IiwgIk1vbm9jeXRlLU5ldXRyb3BoaWwiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXJlZCA9IFQpCgpnZ3Bsb3QobWVnLm5ldXRyby5tdHgucmVjb2xsYXBzZSwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IGNsb25lLm11bHRpLmxhYmVsLCBzaXplID0gaWZlbHNlKHZhbHVlPT0wLCBOQSwgdmFsdWUpLCBjb2xvciA9IHZhcmlhYmxlLCBmaWxsID0gdmFyaWFibGUpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uID0gIkEiLCBiZWdpbiA9IDAuMTUsIGVuZCA9IDAuODUpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAiQSIsIGJlZ2luID0gMC4xNSwgZW5kID0gMC44NSkgKwogIHNjYWxlX3NpemVfYXJlYShuYW1lID0gInBlcmNlbnRhZ2UiLCBtYXhfc2l6ZSA9IDEwKSsKICBsYWJzKHkgPSAiSHlicmlkIElkZW50aXRpZXMiLCB4ID0gIkNsb25hbCBJZGVudGl0aWVzIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQuaXRhbGljIiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkLml0YWxpYyIpKQpgYGAKNykgSGVyZSB3ZSBhcmUgdHJ5aW5nIHRvIGZpbmQgdGhlIGNsb25hbGx5IHJlbGF0ZWQgY2VsbHMgdG8gdGhlIHN0cmljdCBkZWZpbmVkIGRheSA0IGNsb25lcy4gRm9yIGluc3RhbmNlLCB3ZSBhcmUgc2VhcmNoaW5nIGZvciB0aGUgc2libGluZ3MgZm9yIGRheSA0IGNsb25lcyB0aGF0IGFyZSAxMDAlIGNvbXBvc2VkIG9mIE1vbm9jeXRlcyB0byBsb29rIGF0IHRoZWlyIGxpbmVhZ2UgcmVzdHJpY3Rpb24gb24gZGF5IDYuIApgYGB7cn0KI3NmLm9yaSA8LSBjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmEKY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hIDwtIHJiaW5kKGNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYSwgY2xvbmVzLnNzLnN1Yi53aXRoLm5vLnVua25vd24pCiMgY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hIDwtIGNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYVt3aGljaChjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmEkd2l0aC5tdWx0aS5sYWJlbCAhPSAiVW5rbm93biIpLF0KZDQuZDYuY2xvbmFsLmluZm8gPC0gY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hW3doaWNoKGNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYSR0cCAlaW4lIGMoNCw2KSksIF0KbGFiZWxzIDwtIHVuaXF1ZShkNC5kNi5jbG9uYWwuaW5mbyR3aXRoLm11bHRpLmxhYmVsKQpzaW5nbGUubGFiZWxzIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQobGFiZWxzLCAiLSIpLCBmdW5jdGlvbih4KSBsZW5ndGgoeCkgPT0gMSkpCnNpbmdsZS5sYWJlbHMuY2hyIDwtIGxhYmVsc1tzaW5nbGUubGFiZWxzXQoKc3RyaWN0LmNsb25lLmNvbXBvc2l0aW9ucyA8LSBkYXRhLmZyYW1lKCkKc3RyaWN0LmNsb25lcy5hbGwgPC0gbGlzdCgpCgpmb3IgKGN1cnIubCBpbiBzaW5nbGUubGFiZWxzLmNocikgewogIHN0cmljdC5jbG9uZXMgPC0gdW5saXN0KGlkZW50aWZ5LnN0cmljdC5jbG9uZXMoZDQuZDYuY2xvbmFsLmluZm8sIGN1cnIubCkpCiAgZDQuc3RyaWN0IDwtIGNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYVt3aGljaChjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmEkaiAlaW4lIHN0cmljdC5jbG9uZXMpLF0KICBkNC5jbG9uZS5zaXplIDwtIHRhYmxlKGQ0LnN0cmljdCRqKQogIAogIGQ0LnN0cmljdCA8LSBkNC5zdHJpY3Rbd2hpY2goZDQuc3RyaWN0JGogJWluJSBuYW1lcyh3aGljaChkNC5jbG9uZS5zaXplID4gMykpKSwgXQogIAogIGlmIChucm93KGQ0LnN0cmljdCkgPiAwKSB7CiAgICBjdXJyLnN0cmljdC5kNCA8LSBkNC5zdHJpY3Rbd2hpY2goZDQuc3RyaWN0JHRwID09IDQpLCBdCiAgICBjdXJyLnN0cmljdC5kNiA8LSBkNC5zdHJpY3Rbd2hpY2goZDQuc3RyaWN0JHRwID09IDYpLCBdCiAgICAKICAgIHN0cmljdC5jbG9uZXMuYWxsW1tjdXJyLmxdXSA8LSB1bmlxdWUoZDQuc3RyaWN0JGopCiAgICAKICAgIGlmIChucm93KGN1cnIuc3RyaWN0LmQ2KSA+IDApIHsKICAKICAgICAgY3Vyci5zdHJpY3QudG9wLmRvbWluYW50LmZyZXEuZDQgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShjdXJyLnN0cmljdC5kNCR3aXRoLm11bHRpLmxhYmVsKSAqIDEwMC9zdW0odGFibGUoY3Vyci5zdHJpY3QuZDQkd2l0aC5tdWx0aS5sYWJlbCkpKQogICAgICBkNi50b3AuZmF0ZXMgPC0gYXMuY2hhcmFjdGVyKHVuaXF1ZShjdXJyLnN0cmljdC5kNiR3aXRoLm11bHRpLmxhYmVsKVt3aGljaCh1bmlxdWUoY3Vyci5zdHJpY3QuZDYkd2l0aC5tdWx0aS5sYWJlbCkgIT0gIlVua25vd24iKV0pCiAgICAgIGN1cnIuc3RyaWN0LnRvcC5kb21pbmFudC5mcmVxLmQ2IDwtIGN1cnIuc3RyaWN0LmQ2W3doaWNoKGN1cnIuc3RyaWN0LmQ2JHdpdGgubXVsdGkubGFiZWwgJWluJSBkNi50b3AuZmF0ZXMpLF0KICAgICAgZDYuZG9taW4uY29tcCA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGN1cnIuc3RyaWN0LnRvcC5kb21pbmFudC5mcmVxLmQ2JHdpdGgubXVsdGkubGFiZWwpICogMTAwL25yb3coY3Vyci5zdHJpY3QudG9wLmRvbWluYW50LmZyZXEuZDYpKQogICAgICBjdXJyLnN0cmljdC50b3AuZG9taW5hbnQuZnJlcS5kNCRkNC5sYWJlbCA8LSBjdXJyLmwKICAgICAgZDYuZG9taW4uY29tcCRkNC5sYWJlbCA8LSBjdXJyLmwKICAgICAgY3Vyci5zdHJpY3QudG9wLmRvbWluYW50LmZyZXEuZDQkZGF5IDwtICJEYXkgNCIKICAgICAgZDYuZG9taW4uY29tcCRkYXkgPC0gIkRheSA2IgogICAgICAKICAgICAgY3Vyci5zdHJpY3QudG9wLmRvbWluYW50LmZyZXEgPC0gcmJpbmQoY3Vyci5zdHJpY3QudG9wLmRvbWluYW50LmZyZXEuZDQsIGQ2LmRvbWluLmNvbXApCiAgICAgIAogICAgICBpZiAobnJvdyhzdHJpY3QuY2xvbmUuY29tcG9zaXRpb25zKSA8PSAwKSB7CiAgICAgICAgc3RyaWN0LmNsb25lLmNvbXBvc2l0aW9ucyA8LSBjdXJyLnN0cmljdC50b3AuZG9taW5hbnQuZnJlcQogICAgICB9IGVsc2UgewogICAgICAgIHN0cmljdC5jbG9uZS5jb21wb3NpdGlvbnMgPC0gcmJpbmQoc3RyaWN0LmNsb25lLmNvbXBvc2l0aW9ucywgY3Vyci5zdHJpY3QudG9wLmRvbWluYW50LmZyZXEpCiAgICAgIH0KICAgIH0KICB9Cn0KCnN0cmljdC5jbG9uZS5jb21wb3NpdGlvbnMkbmV3LmxhYmVsIDwtIHBhc3RlMChzdHJpY3QuY2xvbmUuY29tcG9zaXRpb25zJGRheSwgIl8iLCBzdHJpY3QuY2xvbmUuY29tcG9zaXRpb25zJFZhcjEpCmR0IDwtIHNldERUKHN0cmljdC5jbG9uZS5jb21wb3NpdGlvbnMpCmR0Lm10eCA8LSBkY2FzdChkdCwgZDQubGFiZWx+bmV3LmxhYmVsLCBmaWxsID0gMCwgdmFsdWUudmFyID0gIkZyZXEiLCBmdW4uYWdncmVnYXRlID0gbWVhbikKCmR0Lm10eC5yZWNvbGxhcHNlIDwtIHJlc2hhcGUyOjptZWx0KGR0Lm10eCkKYGBgCgo4KSBIZXJlIHdlIGFyZSB0cnlpbmcgdG8gZmluZCB0aGUgY2xvbmFsbHkgcmVsYXRlZCBjZWxscyB0byB0aGUgaHlicmlkIGNlbGxzIGRlZmluZWQgb24gRGF5IDQsIHBhcnRpY3VsYXJseSBpbiB0aGlzIGNhc2UsIHRlc3RpbmcgbW9ub2N5dGUtbmV1dHJvcGhpbCBoeWJyaWRzIGFuZCBiYXNvcGhpbC1tYXN0IGNlbGwgaHlicmlkcy4KYGBge3J9CnNpbmdsZS5yZWNvbGxhcHNlIDwtIGR0Lm10eC5yZWNvbGxhcHNlCgpkb3VibGUubGFiZWxzIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQobGFiZWxzLCAiLSIpLCBmdW5jdGlvbih4KSBsZW5ndGgoeCkgPT0gMikpCmRvdWJsZS5sYWJlbHMuY2hyIDwtIGxhYmVsc1tkb3VibGUubGFiZWxzXQoKZGwuY2xvbmUuY29tcG9zaXRpb25zIDwtIGRhdGEuZnJhbWUoKQpkbC5jbG9uZXMuYWxsIDwtIGxpc3QoKQoKZm9yIChjdXJyLmRsIGluIGRvdWJsZS5sYWJlbHMuY2hyKSB7CiAgY2xvbmVzLndpdGguY3Vyci5kbCA8LSB1bmlxdWUoY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hJGpbd2hpY2goY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hJHdpdGgubXVsdGkubGFiZWwgPT0gY3Vyci5kbCldKQogIGQ0LnN0cmljdCA8LSBjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmFbd2hpY2goY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hJGogJWluJSBjbG9uZXMud2l0aC5jdXJyLmRsKSxdCiAgCiAgZDQuY2xvbmUuc2l6ZSA8LSB0YWJsZShkNC5zdHJpY3QkaikKICAKICBkNC5zdHJpY3QgPC0gZDQuc3RyaWN0W3doaWNoKGQ0LnN0cmljdCRqICVpbiUgbmFtZXMod2hpY2goZDQuY2xvbmUuc2l6ZSA+PSAzKSkpLCBdCiAgCiAgaWYgKG5yb3coZDQuc3RyaWN0KSA+IDApIHsKICAgIAogICAgY3Vyci5zdHJpY3QuZDQgPC0gZDQuc3RyaWN0W3doaWNoKGQ0LnN0cmljdCR0cCA9PSA0KSwgXQogICAgY3Vyci5zdHJpY3QuZDYgPC0gZDQuc3RyaWN0W3doaWNoKGQ0LnN0cmljdCR0cCA9PSA2KSwgXQogICAgCiAgICBkbC5jbG9uZXMuYWxsW1tjdXJyLmRsXV0gPC0gdW5pcXVlKGQ0LnN0cmljdCRqKQogICAgaWYgKG5yb3coY3Vyci5zdHJpY3QuZDQpID09IDApIG5leHQKICAgIAogICAgaWYgKG5yb3coY3Vyci5zdHJpY3QuZDYpID4gMCkgewogIAogICAgICBjdXJyLnN0cmljdC50b3AuZG9taW5hbnQuZnJlcS5kNCA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGN1cnIuc3RyaWN0LmQ0W3doaWNoKGN1cnIuc3RyaWN0LmQ0JHdpdGgubXVsdGkubGFiZWwgIT0gIlVua25vd24iKSwgIndpdGgubXVsdGkubGFiZWwiXSkgKiAxMDAvKHN1bSh0YWJsZShjdXJyLnN0cmljdC5kNFt3aGljaChjdXJyLnN0cmljdC5kNCR3aXRoLm11bHRpLmxhYmVsICE9ICJVbmtub3duIiksICJ3aXRoLm11bHRpLmxhYmVsIl0pKSkpCiAgICAgIGQ2LnRvcC5mYXRlcyA8LSBhcy5jaGFyYWN0ZXIodW5pcXVlKGN1cnIuc3RyaWN0LmQ2JHdpdGgubXVsdGkubGFiZWwpW3doaWNoKHVuaXF1ZShjdXJyLnN0cmljdC5kNiR3aXRoLm11bHRpLmxhYmVsKSAhPSAiVW5rbm93biIpXSkKICAgICAgY3Vyci5zdHJpY3QudG9wLmRvbWluYW50LmZyZXEuZDYgPC0gY3Vyci5zdHJpY3QuZDZbd2hpY2goY3Vyci5zdHJpY3QuZDYkd2l0aC5tdWx0aS5sYWJlbCAlaW4lIGQ2LnRvcC5mYXRlcyksXQogICAgICBkNi5kb21pbi5jb21wIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoY3Vyci5zdHJpY3QudG9wLmRvbWluYW50LmZyZXEuZDYkd2l0aC5tdWx0aS5sYWJlbCkgKiAxMDAvbnJvdyhjdXJyLnN0cmljdC50b3AuZG9taW5hbnQuZnJlcS5kNikpCiAgICAgIGN1cnIuc3RyaWN0LnRvcC5kb21pbmFudC5mcmVxLmQ0JGQ0LmxhYmVsIDwtIGN1cnIuZGwKICAgICAgZDYuZG9taW4uY29tcCRkNC5sYWJlbCA8LSBjdXJyLmRsCiAgICAgIGN1cnIuc3RyaWN0LnRvcC5kb21pbmFudC5mcmVxLmQ0JGRheSA8LSAiRGF5IDQiCiAgICAgIGQ2LmRvbWluLmNvbXAkZGF5IDwtICJEYXkgNiIKICAgICAgCiAgICAgIGN1cnIuc3RyaWN0LnRvcC5kb21pbmFudC5mcmVxIDwtIHJiaW5kKGN1cnIuc3RyaWN0LnRvcC5kb21pbmFudC5mcmVxLmQ0LCBkNi5kb21pbi5jb21wKQogICAgICAKICAgICAgaWYgKG5yb3coZGwuY2xvbmUuY29tcG9zaXRpb25zKSA8PSAwKSB7CiAgICAgICAgZGwuY2xvbmUuY29tcG9zaXRpb25zIDwtIGN1cnIuc3RyaWN0LnRvcC5kb21pbmFudC5mcmVxCiAgICAgIH0gZWxzZSB7CiAgICAgICAgZGwuY2xvbmUuY29tcG9zaXRpb25zIDwtIHJiaW5kKGRsLmNsb25lLmNvbXBvc2l0aW9ucywgY3Vyci5zdHJpY3QudG9wLmRvbWluYW50LmZyZXEpCiAgICAgIH0KICB9CiAgfQp9CgpkbC5jbG9uZS5jb21wb3NpdGlvbnMkbmV3LmxhYmVsIDwtIHBhc3RlMChkbC5jbG9uZS5jb21wb3NpdGlvbnMkZGF5LCAiXyIsIGRsLmNsb25lLmNvbXBvc2l0aW9ucyRWYXIxKQpkdCA8LSBzZXREVChkbC5jbG9uZS5jb21wb3NpdGlvbnMpCmR0Lm10eCA8LSBkY2FzdChkdCwgZDQubGFiZWx+bmV3LmxhYmVsLCBmaWxsID0gMCwgdmFsdWUudmFyID0gIkZyZXEiLCBmdW4uYWdncmVnYXRlID0gbWVhbikKCmR0Lm10eC5yZWNvbGxhcHNlIDwtIHJlc2hhcGUyOjptZWx0KGR0Lm10eCkKYGBgCgo5KSBXZSBjb21iaW5lIHRoZSBzdHJpY3QgY2xvbmUgZGF0YSBhbmQgdGhlIGh5YnJpZCBjbG9uYWwgZGF0YS4KYGBge3J9CmR0Lm10eC5yZWNvbGxhcHNlJHZhcmlhYmxlIDwtIGFzLmNoYXJhY3RlcihkdC5tdHgucmVjb2xsYXBzZSR2YXJpYWJsZSkKZHQubXR4LnJlY29sbGFwc2UkY3QgPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdChkdC5tdHgucmVjb2xsYXBzZSR2YXJpYWJsZSwgIl8iKSwgZnVuY3Rpb24oeCkgeFtsZW5ndGgoeCldKSkKZHQubXR4LnJlY29sbGFwc2UkdHAgPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdChkdC5tdHgucmVjb2xsYXBzZSR2YXJpYWJsZSwgIl8iKSwgZnVuY3Rpb24oeCkgeFsxXSkpCgpzaW5nbGUucmVjb2xsYXBzZSR2YXJpYWJsZSA8LSBhcy5jaGFyYWN0ZXIoc2luZ2xlLnJlY29sbGFwc2UkdmFyaWFibGUpCnNpbmdsZS5yZWNvbGxhcHNlJGN0IDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQoc2luZ2xlLnJlY29sbGFwc2UkdmFyaWFibGUsICJfIiksIGZ1bmN0aW9uKHgpIHhbbGVuZ3RoKHgpXSkpCnNpbmdsZS5yZWNvbGxhcHNlJHRwIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQoc2luZ2xlLnJlY29sbGFwc2UkdmFyaWFibGUsICJfIiksIGZ1bmN0aW9uKHgpIHhbMV0pKQoKZHQubXR4LnJlY29sbGFwc2Uuc3ViIDwtIGR0Lm10eC5yZWNvbGxhcHNlW3doaWNoKGR0Lm10eC5yZWNvbGxhcHNlJGQ0LmxhYmVsICVpbiUgYygiTW9ub2N5dGUtTmV1dHJvcGhpbCIsICJCYXNvLUVvcyIsICJCYXNvLU1hc3QiKSksIF0KCnNpbmdsZS5yZWNvbGxhcHNlLnN1YiA8LSBzaW5nbGUucmVjb2xsYXBzZVt3aGljaChzaW5nbGUucmVjb2xsYXBzZSRkNC5sYWJlbCAhPSAiVW5rbm93biIpLCBdCnNpbmdsZS5kb3VibGUucmVjb2xsYXBzZSA8LSByYmluZChzaW5nbGUucmVjb2xsYXBzZS5zdWIsIGR0Lm10eC5yZWNvbGxhcHNlLnN1YikKYGBgCgoxMCkgV2UgdGFrZSBhIGxvb2sgYXQgdGhlc2UgaHlicmlkIGNsb25lcywgY29tcGFyZWQgdG8gdGhlaXIgZGlzY3JldGUgY291bnRlciBwYXJ0cy4KSSkgTW9ub2N5dGUtTmV1dHJvcGhpbApgYGB7cn0Kc3Vic2V0Lm1vbm8ubmV1dHJvIDwtIHNpbmdsZS5kb3VibGUucmVjb2xsYXBzZVt3aGljaChzaW5nbGUuZG91YmxlLnJlY29sbGFwc2UkZDQubGFiZWwgJWluJSBjKCJNb25vY3l0ZSIsICJOZXV0cm9waGlsIiwgIk1vbm9jeXRlLU5ldXRyb3BoaWwiKSksIF0KCnN1YnNldC5tb25vLm5ldXRybyA8LSBzdWJzZXQubW9uby5uZXV0cm9bLXdoaWNoKHN1YnNldC5tb25vLm5ldXRybyR2YWx1ZSA9PSAwKSxdCgpzdWJzZXQubW9uby5uZXV0cm8kY3QgPC0gZmFjdG9yKHN1YnNldC5tb25vLm5ldXRybyRjdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJNb25vY3l0ZSIsICJOZXV0cm9waGlsIiwgIk1vbm9jeXRlLU5ldXRyb3BoaWwiLCAiQmFzbyIsICJNYXN0IiwgIkVvcyIsICJCYXNvLUVvcyIsICJVbmtub3duIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXJlZCA9IFQpCnN1YnNldC5tb25vLm5ldXRybyRkNC5sYWJlbCA8LSBmYWN0b3Ioc3Vic2V0Lm1vbm8ubmV1dHJvJGQ0LmxhYmVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IHJldihjKCJNb25vY3l0ZSIsICJOZXV0cm9waGlsIiwgIk1vbm9jeXRlLU5ldXRyb3BoaWwiKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXJlZCA9IFQpCmdncGxvdChzdWJzZXQubW9uby5uZXV0cm8sIGFlcyh4ID0gY3QsIHkgPSBkNC5sYWJlbCwgc2l6ZSA9IGlmZWxzZSh2YWx1ZT09MCwgTkEsIHZhbHVlKSwgY29sb3IgPSBjdCwgZmlsbCA9IGN0KSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbiA9ICJBIiwgYmVnaW4gPSAwLjE1LCBlbmQgPSAwLjg1KSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gIkEiLCBiZWdpbiA9IDAuMTUsIGVuZCA9IDAuODUpICsKICBzY2FsZV9zaXplX2FyZWEobmFtZSA9ICJwZXJjZW50YWdlIiwgbWF4X3NpemUgPSAxMCkrCiAgZmFjZXRfZ3JpZCgufnRwKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQuaXRhbGljIiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IiwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZC5pdGFsaWMiKSkKYGBgCklJKSBCYXNvcGhpbC1NYXN0CmBgYHtyfQpzdWJzZXQuYmFzby5tYXN0IDwtIHNpbmdsZS5kb3VibGUucmVjb2xsYXBzZVt3aGljaChzaW5nbGUuZG91YmxlLnJlY29sbGFwc2UkZDQubGFiZWwgJWluJSBjKCJCYXNvIiwgIk1hc3QiLCAiQmFzby1NYXN0IikpLCBdCnN1YnNldC5iYXNvLm1hc3QgPC0gc3Vic2V0LmJhc28ubWFzdFstd2hpY2goc3Vic2V0LmJhc28ubWFzdCR2YWx1ZSA9PSAwKSwgXQoKc3Vic2V0LmJhc28ubWFzdCRjdCA8LSBmYWN0b3Ioc3Vic2V0LmJhc28ubWFzdCRjdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJCYXNvIiwgIk1hc3QiLCAiQmFzby1NYXN0IiwgIk1vbm9jeXRlIiwgIk5ldXRyb3BoaWwiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlcmVkID0gVCkKc3Vic2V0LmJhc28ubWFzdCRkNC5sYWJlbCA8LSBmYWN0b3Ioc3Vic2V0LmJhc28ubWFzdCRkNC5sYWJlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSByZXYoYygiQmFzbyIsICJNYXN0IiwgIkJhc28tTWFzdCIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlcmVkID0gVCkKZ2dwbG90KHN1YnNldC5iYXNvLm1hc3QsIGFlcyh4ID0gY3QsIHkgPSBkNC5sYWJlbCwgc2l6ZSA9IGlmZWxzZSh2YWx1ZT09MCwgTkEsIHZhbHVlKSwgY29sb3IgPSBjdCwgZmlsbCA9IGN0KSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbiA9ICJBIiwgYmVnaW4gPSAwLjE1LCBlbmQgPSAwLjg1KSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gIkEiLCBiZWdpbiA9IDAuMTUsIGVuZCA9IDAuODUpICsKICBzY2FsZV9zaXplX2FyZWEobmFtZSA9ICJwZXJjZW50YWdlIiwgbWF4X3NpemUgPSAxMCkrCiAgZmFjZXRfZ3JpZCgufnRwKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQuaXRhbGljIiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IiwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZC5pdGFsaWMiKSkKYGBgCgoxMSkgSGVyZSB3ZSB0YWtlIGEgY2xvc2VyIGxvb2sgYXQgdGhlIG1vbm9jeXRlLW5ldXRyb3BoaWwgaHlicmlkcy4gQWdhaW4sIHdlIGZpcnN0IGxvb2sgYXQgdGhlIGxpbmVhZ2UgcmVzdHJpY3RlZCBkYXkgNCBjbG9uZXMgYW5kIHRoZWlyIGNvcnJlbGF0ZWQgZGF5IDYgc2libGluZ3MuIFRoZW4gd2UgbG9vayBhdCB0aGUgaHlicmlkcy4KYGBge3J9CnVuaXF1ZS5jbG9uZXMgPC0gdW5pcXVlKGNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYSRqKQptb25vY3l0ZS5jbG9uZXMuZDQgPC0gYygpCm5ldXRyby5jbG9uZXMuZDQgPC0gYygpCmZvciAodWMgaW4gdW5pcXVlLmNsb25lcykgewogIGN1cnIuY2xvbmUgPC0gY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hW3doaWNoKGNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYSRqID09IHVjKSwgXQogIGN1cnIuY2xvbmUuZDQgPC0gY3Vyci5jbG9uZVt3aGljaChjdXJyLmNsb25lJHRwID09IDQpLF0KICBpZiAobnJvdyhjdXJyLmNsb25lLmQ0KSA+IDApIHsKICAgIGNlbGwudHlwZS5jbG9uZS5kNCA8LSB1bmlxdWUoY3Vyci5jbG9uZS5kNCR3aXRoLm11bHRpLmxhYmVsKQogICAgaWYgKGxlbmd0aChjZWxsLnR5cGUuY2xvbmUuZDQpID09IDEpIHsKICAgICAgaWYgKGNlbGwudHlwZS5jbG9uZS5kNCA9PSAiTW9ub2N5dGUiKSB7CiAgICAgICAgbW9ub2N5dGUuY2xvbmVzLmQ0IDwtIGMobW9ub2N5dGUuY2xvbmVzLmQ0LCB1YykKICAgICAgfQogICAgICBpZiAoY2VsbC50eXBlLmNsb25lLmQ0ID09ICJOZXV0cm9waGlsIikgewogICAgICAgIG5ldXRyby5jbG9uZXMuZDQgPC0gYyhuZXV0cm8uY2xvbmVzLmQ0LCB1YykKICAgICAgfQogICAgfQogIH0KfQoKbW9uby5kNC5zdHJpY3QgPC0gY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hW3doaWNoKGNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYSRqICVpbiUgc3RyaWN0LmNsb25lcy5hbGxbWyJNb25vY3l0ZSJdXSksXQpuZXV0cm8uZDQuc3RyaWN0IDwtIGNsb25lLmFubm8uc3RhdGUuZmF0ZS5uby5uYVt3aGljaChjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmEkaiAlaW4lIHN0cmljdC5jbG9uZXMuYWxsW1siTmV1dHJvcGhpbCJdXSksXQptb25vLm5ldXRyby5kNC5jbG9uZSA8LSBjbG9uZS5hbm5vLnN0YXRlLmZhdGUubm8ubmFbd2hpY2goY2xvbmUuYW5uby5zdGF0ZS5mYXRlLm5vLm5hJGogJWluJSBkbC5jbG9uZXMuYWxsW1siTW9ub2N5dGUtTmV1dHJvcGhpbCJdXSksXQoKbW9uby5kNi5zdHJpY3QgPC0gbW9uby5kNC5zdHJpY3Rbd2hpY2gobW9uby5kNC5zdHJpY3QkdHAgJWluJSBjKDQsNikpLCBdCm5ldXRyby5kNi5zdHJpY3QgPC0gbmV1dHJvLmQ0LnN0cmljdFt3aGljaChuZXV0cm8uZDQuc3RyaWN0JHRwICVpbiUgYyg0LDYpKSwgXQptb25vLm5ldXRyby5kNi5zdHJpY3QgPC0gbW9uby5uZXV0cm8uZDQuY2xvbmVbd2hpY2gobW9uby5uZXV0cm8uZDQuY2xvbmUkdHAgJWluJSBjKDQsNikpLCBdCgptb25vLm5ldXRyby5kNi5zdHJpY3QuZG9tLmZhdGUgPC0gY29tcHV0ZS5jb21wb3NpdGlvbi5jbG9uZXMoY2xvbmFsLmRhdGEgPSBtb25vLm5ldXRyby5kNi5zdHJpY3QsICJ3aXRoLm11bHRpLmxhYmVsIikKCmQ2LmNlbGxzLnNpbmdsZXMuZnJlcSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKG1vbm8uZDYuc3RyaWN0JHdpdGgubXVsdGkubGFiZWwpICogMTAwL25yb3cobW9uby5kNi5zdHJpY3QpKQpgYGAKCkkpIE5ldXRyb3BoaWwgc3RyaWN0IGNsb25lcwpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9OH0KZDYuY2VsbHMuc2luZ2xlcy5mcmVxIDwtIGFzLmRhdGEuZnJhbWUodGFibGUobmV1dHJvLmQ2LnN0cmljdCR3aXRoLm11bHRpLmxhYmVsKSAqIDEwMC9ucm93KG5ldXRyby5kNi5zdHJpY3QpKQpwbG90LmJhci5jaGFydChkNi5jZWxscy5zaW5nbGVzLmZyZXEsICJ+L0Rlc2t0b3AvbGFycnlfZDRfZDZfbmV1dHJvX2Jhci5wZGYiLCA1LCA3LCAiZDYgc2libGluZ3MiKQpgYGAKCklJKSBNb25vY3l0ZSBzdHJpY3QgY2xvbmVzCmBgYHtyLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD04fQpkNi5jZWxscy5zaW5nbGVzLmZyZXEgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShtb25vLmQ2LnN0cmljdFt3aGljaChtb25vLmQ2LnN0cmljdCR3aXRoLm11bHRpLmxhYmVsICE9ICJVbmtub3duIiksICJ3aXRoLm11bHRpLmxhYmVsIl0pICogMTAwL25yb3cobW9uby5kNi5zdHJpY3QpKQpwbG90LmJhci5jaGFydChkNi5jZWxscy5zaW5nbGVzLmZyZXEsICJ+L0Rlc2t0b3AvbGFycnlfZDRfZDZfbW9ub19iYXJfMi5wZGYiLCA1LCA3LCAiZDYgc2libGluZ3MiKQpgYGAKCgpJSUkpIE1vbm9jeXRlLU5ldXRyb3BoaWwgaHlicmlkIGNsb25lcwpgYGB7ciwgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9OH0KZDYuY2VsbHMuc2luZ2xlcy5mcmVxIDwtIGFzLmRhdGEuZnJhbWUodGFibGUobW9uby5uZXV0cm8uZDYuc3RyaWN0W3doaWNoKG1vbm8ubmV1dHJvLmQ2LnN0cmljdCR3aXRoLm11bHRpLmxhYmVsICE9ICJVbmtub3duIiksICJ3aXRoLm11bHRpLmxhYmVsIl0pICogMTAwL25yb3cobW9uby5uZXV0cm8uZDYuc3RyaWN0KSkKcGxvdC5iYXIuY2hhcnQoZDYuY2VsbHMuc2luZ2xlcy5mcmVxLCAifi9EZXNrdG9wL2xhcnJ5X2Q0X2Q2X21vbm9fbmV1dHJvX2Jhcl8yLnBkZiIsIDUsIDcsICJkNiBzaWJsaW5ncyIpCmBgYAoKT3ZlcmFsbCwgd2UgaGF2ZSBzaG93biB3aXRoIHRoZSBsaW5lYWdlIGRhdGEgdGhhdCB0aGVzZSBoeWJyaWQgY2VsbHMgYXJlIGxpbmVhZ2UgcmVzdHJpY3RlZCBmcm9tIHRoZWlyIGRheSA0IHRvIGRheSA2IHNpYmxpbmdzLgoKCgo=